codexmate 0.0.56 → 0.0.57

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 (31) hide show
  1. package/cli.js +680 -129
  2. package/lib/task-orchestrator.js +90 -21
  3. package/lib/task-workspace-chat.js +292 -0
  4. package/package.json +2 -2
  5. package/web-ui/app.js +55 -129
  6. package/web-ui/modules/app.computed.main-tabs.mjs +304 -7
  7. package/web-ui/modules/app.methods.agents.mjs +3 -0
  8. package/web-ui/modules/app.methods.claude-config.mjs +65 -25
  9. package/web-ui/modules/app.methods.navigation.mjs +67 -83
  10. package/web-ui/modules/app.methods.openclaw-editing.mjs +3 -6
  11. package/web-ui/modules/app.methods.session-actions.mjs +1 -12
  12. package/web-ui/modules/app.methods.session-browser.mjs +23 -54
  13. package/web-ui/modules/app.methods.session-trash.mjs +0 -1
  14. package/web-ui/modules/app.methods.startup-claude.mjs +16 -31
  15. package/web-ui/modules/app.methods.task-orchestration.mjs +347 -8
  16. package/web-ui/modules/app.methods.tool-config-permissions.mjs +0 -3
  17. package/web-ui/modules/app.methods.web-ui-preferences.mjs +415 -68
  18. package/web-ui/modules/config-template-confirm-pref.mjs +2 -3
  19. package/web-ui/modules/i18n/locales/en.mjs +146 -26
  20. package/web-ui/modules/i18n/locales/ja.mjs +143 -23
  21. package/web-ui/modules/i18n/locales/vi.mjs +145 -25
  22. package/web-ui/modules/i18n/locales/zh-tw.mjs +146 -26
  23. package/web-ui/modules/i18n/locales/zh.mjs +148 -28
  24. package/web-ui/modules/i18n.mjs +5 -12
  25. package/web-ui/modules/sessions-filters-url.mjs +1 -2
  26. package/web-ui/partials/index/layout-header.html +37 -14
  27. package/web-ui/partials/index/panel-orchestration.html +489 -282
  28. package/web-ui/res/web-ui-render.precompiled.js +1049 -610
  29. package/web-ui/styles/layout-shell.css +157 -1
  30. package/web-ui/styles/navigation-panels.css +11 -0
  31. package/web-ui/styles/task-orchestration.css +2161 -4
@@ -4,8 +4,7 @@
4
4
  switchMainTabHelper,
5
5
  loadMoreSessionMessagesHelper
6
6
  } = options;
7
- const NAV_STATE_STORAGE_KEY = 'codexmateNavState.v1';
8
- const MAIN_TAB_SET = new Set([
7
+ const MAIN_TAB_ORDER = [
9
8
  'dashboard',
10
9
  'config',
11
10
  'sessions',
@@ -17,7 +16,39 @@
17
16
  'settings',
18
17
  'trash',
19
18
  'prompts'
20
- ]);
19
+ ];
20
+ const MAIN_TAB_SET = new Set(MAIN_TAB_ORDER);
21
+ const DISABLED_MAIN_TAB_SET = new Set(['orchestration']);
22
+ const normalizeMainTab = (tab) => typeof tab === 'string'
23
+ ? tab.trim().toLowerCase()
24
+ : '';
25
+ const isMainTabSelectable = (tab) => {
26
+ const normalized = normalizeMainTab(tab);
27
+ return !!(normalized && MAIN_TAB_SET.has(normalized) && !DISABLED_MAIN_TAB_SET.has(normalized));
28
+ };
29
+ const getFirstSelectableMainTab = () => MAIN_TAB_ORDER.find((tab) => isMainTabSelectable(tab)) || 'dashboard';
30
+ const resolveSelectableMainTab = (tab) => {
31
+ const normalized = normalizeMainTab(tab);
32
+ if (isMainTabSelectable(normalized)) return normalized;
33
+ return getFirstSelectableMainTab();
34
+ };
35
+ const resolveSwitchMainTabTarget = (tab) => {
36
+ const normalized = normalizeMainTab(tab);
37
+ if (!normalized) return '';
38
+ if (isMainTabSelectable(normalized)) return normalized;
39
+ if (MAIN_TAB_SET.has(normalized) && DISABLED_MAIN_TAB_SET.has(normalized)) {
40
+ return getFirstSelectableMainTab();
41
+ }
42
+ return '';
43
+ };
44
+ const cancelDisabledMainTabEvent = (event) => {
45
+ if (event && typeof event.preventDefault === 'function') {
46
+ event.preventDefault();
47
+ }
48
+ if (event && typeof event.stopPropagation === 'function') {
49
+ event.stopPropagation();
50
+ }
51
+ };
21
52
  const loadDoctorOverview = async (vm, options = {}) => {
22
53
  if (!vm || typeof vm !== 'object') return false;
23
54
  if (vm.__doctorLoading) return false;
@@ -41,23 +72,6 @@
41
72
  }
42
73
  }
43
74
  };
44
- const readNavState = () => {
45
- if (typeof localStorage === 'undefined') return null;
46
- let raw = '';
47
- try {
48
- raw = localStorage.getItem(NAV_STATE_STORAGE_KEY) || '';
49
- } catch (_) {
50
- raw = '';
51
- }
52
- if (!raw) return null;
53
- try {
54
- const parsed = JSON.parse(raw);
55
- return parsed && typeof parsed === 'object' ? parsed : null;
56
- } catch (_) {
57
- return null;
58
- }
59
- };
60
-
61
75
  const canonicalizeWebUiRuntimeUrl = () => {
62
76
  if (typeof window === 'undefined' || !window.location) return;
63
77
  try {
@@ -85,7 +99,6 @@
85
99
 
86
100
  const persistNavState = (vm, overrides = null) => {
87
101
  if (!vm || vm.__navStateRestoring) return;
88
- if (typeof localStorage === 'undefined') return;
89
102
  const resolvedOverrides = overrides && typeof overrides === 'object' ? overrides : null;
90
103
  const mainTabSource = resolvedOverrides && typeof resolvedOverrides.mainTab === 'string'
91
104
  ? resolvedOverrides.mainTab
@@ -93,76 +106,36 @@
93
106
  const configModeSource = resolvedOverrides && typeof resolvedOverrides.configMode === 'string'
94
107
  ? resolvedOverrides.configMode
95
108
  : vm.configMode;
96
- const mainTab = typeof mainTabSource === 'string' ? mainTabSource.trim().toLowerCase() : '';
109
+ const mainTab = normalizeMainTab(mainTabSource);
97
110
  const configMode = typeof configModeSource === 'string' ? configModeSource.trim().toLowerCase() : '';
98
111
  const settingsTab = typeof vm.settingsTab === 'string' ? vm.settingsTab.trim().toLowerCase() : 'general';
99
112
  const skillsTargetApp = typeof vm.skillsTargetApp === 'string' && (vm.skillsTargetApp === 'codex' || vm.skillsTargetApp === 'claude') ? vm.skillsTargetApp : 'codex';
100
113
  const promptTemplatesMode = typeof vm.promptTemplatesMode === 'string' && (vm.promptTemplatesMode === 'compose' || vm.promptTemplatesMode === 'manage') ? vm.promptTemplatesMode : 'compose';
101
114
  const snapshot = {
102
115
  settingsTab: settingsTab === 'data' ? 'data' : 'general',
103
- mainTab: MAIN_TAB_SET.has(mainTab) ? mainTab : 'dashboard',
116
+ mainTab: resolveSelectableMainTab(mainTab),
104
117
  configMode: configModeSet && configModeSet.has(configMode) ? configMode : 'codex',
105
118
  skillsTargetApp,
106
119
  promptTemplatesMode
107
120
  };
108
- try {
109
- localStorage.setItem(NAV_STATE_STORAGE_KEY, JSON.stringify(snapshot));
110
- } catch (_) {}
111
121
  if (typeof vm.persistWebUiPreferences === 'function') {
112
122
  vm.persistWebUiPreferences({ navigation: snapshot });
113
123
  }
114
124
  };
115
125
 
116
126
  return {
127
+ toggleSidebarCollapsed() {
128
+ this.sidebarCollapsed = !this.sidebarCollapsed;
129
+ if (typeof this.persistWebUiPreferences === 'function') {
130
+ this.persistWebUiPreferences({ sidebarCollapsed: this.sidebarCollapsed });
131
+ }
132
+ },
133
+
117
134
  saveNavState() {
118
135
  persistNavState(this);
119
136
  },
120
137
  restoreNavStateFromStorage() {
121
- if (this.__navStateRestoring) return false;
122
- const restored = readNavState();
123
- if (!restored) return false;
124
- const nextMainTab = restored && typeof restored.mainTab === 'string'
125
- ? restored.mainTab.trim().toLowerCase()
126
- : '';
127
- const nextConfigMode = restored && typeof restored.configMode === 'string'
128
- ? restored.configMode.trim().toLowerCase()
129
- : '';
130
- const shouldUpdateConfigMode = !!(nextConfigMode && configModeSet && configModeSet.has(nextConfigMode));
131
- const shouldUpdateMainTab = !!(nextMainTab && MAIN_TAB_SET.has(nextMainTab) && nextMainTab !== this.mainTab);
132
- const nextSettingsTab = restored && typeof restored.settingsTab === 'string'
133
- ? restored.settingsTab.trim().toLowerCase()
134
- : '';
135
- const shouldUpdateSettingsTab = !!(nextSettingsTab && (nextSettingsTab === 'general' || nextSettingsTab === 'data') && nextSettingsTab !== this.settingsTab);
136
- const nextSkillsTargetApp = restored && typeof restored.skillsTargetApp === 'string' && (restored.skillsTargetApp === 'codex' || restored.skillsTargetApp === 'claude')
137
- ? restored.skillsTargetApp : '';
138
- const shouldUpdateSkillsTargetApp = !!(nextSkillsTargetApp && nextSkillsTargetApp !== this.skillsTargetApp);
139
- const nextPromptTemplatesMode = restored && typeof restored.promptTemplatesMode === 'string' && (restored.promptTemplatesMode === 'compose' || restored.promptTemplatesMode === 'manage')
140
- ? restored.promptTemplatesMode : '';
141
- const shouldUpdatePromptTemplatesMode = !!(nextPromptTemplatesMode && nextPromptTemplatesMode !== this.promptTemplatesMode);
142
- if (!shouldUpdateConfigMode && !shouldUpdateMainTab && !shouldUpdateSettingsTab && !shouldUpdateSkillsTargetApp && !shouldUpdatePromptTemplatesMode) {
143
- return false;
144
- }
145
- this.__navStateRestoring = true;
146
- try {
147
- if (shouldUpdateConfigMode) {
148
- this.configMode = nextConfigMode;
149
- }
150
- if (shouldUpdateSettingsTab) {
151
- this.settingsTab = nextSettingsTab;
152
- }
153
- if (shouldUpdateMainTab) {
154
- this.switchMainTab(nextMainTab);
155
- }
156
- if (shouldUpdateSkillsTargetApp) {
157
- this.skillsTargetApp = nextSkillsTargetApp;
158
- }
159
- if (shouldUpdatePromptTemplatesMode) {
160
- this.promptTemplatesMode = nextPromptTemplatesMode;
161
- }
162
- } finally {
163
- this.__navStateRestoring = false;
164
- }
165
- return true;
138
+ return false;
166
139
  },
167
140
  switchConfigMode(mode) {
168
141
  const normalizedMode = typeof mode === 'string'
@@ -353,6 +326,10 @@
353
326
  }
354
327
  const normalizedTab = typeof tab === 'string' ? tab.trim().toLowerCase() : '';
355
328
  if (!normalizedTab) return;
329
+ if (!isMainTabSelectable(normalizedTab)) {
330
+ cancelDisabledMainTabEvent(event);
331
+ return;
332
+ }
356
333
  persistNavState(this, { mainTab: normalizedTab });
357
334
  this.setMainTabSwitchIntent(normalizedTab);
358
335
  this.applyImmediateNavIntent(normalizedTab);
@@ -394,8 +371,13 @@
394
371
  this.switchConfigMode(normalizedMode);
395
372
  },
396
373
  onMainTabClick(tab) {
374
+ const event = arguments.length > 1 ? arguments[1] : null;
397
375
  const normalizedTab = typeof tab === 'string' ? tab.trim().toLowerCase() : '';
398
376
  if (!normalizedTab) return;
377
+ if (!isMainTabSelectable(normalizedTab)) {
378
+ cancelDisabledMainTabEvent(event);
379
+ return;
380
+ }
399
381
  if (this.consumePointerNavCommit('main', normalizedTab)) return;
400
382
  this.switchMainTab(normalizedTab);
401
383
  },
@@ -437,16 +419,20 @@
437
419
  }
438
420
  return this.configMode === mode;
439
421
  },
422
+ isMainTabDisabled(tab) {
423
+ const normalizedTab = normalizeMainTab(tab);
424
+ return !!(normalizedTab && MAIN_TAB_SET.has(normalizedTab) && DISABLED_MAIN_TAB_SET.has(normalizedTab));
425
+ },
426
+ getFirstSelectableMainTab() {
427
+ return getFirstSelectableMainTab();
428
+ },
440
429
  switchMainTab(tab) {
441
430
  const normalizedTab = typeof tab === 'string'
442
431
  ? tab.trim().toLowerCase()
443
432
  : '';
444
- const targetTab = normalizedTab || tab;
433
+ const targetTab = resolveSwitchMainTabTarget(normalizedTab || tab);
445
434
  if (!targetTab) return;
446
435
  canonicalizeWebUiRuntimeUrl();
447
- if (targetTab === 'orchestration' && this.taskOrchestrationTabEnabled !== true) {
448
- return this.switchMainTab('config');
449
- }
450
436
  persistNavState(this, {
451
437
  mainTab: targetTab,
452
438
  configMode: targetTab === 'config' ? this.configMode : this.configMode
@@ -473,13 +459,11 @@
473
459
  switchState.ticket += 1;
474
460
  switchState.pendingTarget = '';
475
461
  if (targetTab === 'dashboard' && !this.__doctorLoadedOnce) {
476
- if (targetTab === 'trash' && !this.sessionTrashLoadedOnce) {
477
- if (typeof this.loadSessionTrash === 'function') {
478
- void this.loadSessionTrash({ forceRefresh: false });
479
- }
480
- }
481
462
  void loadDoctorOverview(this);
482
463
  }
464
+ if (targetTab === 'trash' && !this.sessionTrashLoadedOnce && typeof this.loadSessionTrash === 'function') {
465
+ void this.loadSessionTrash({ forceRefresh: false });
466
+ }
483
467
  if (
484
468
  targetTab === 'sessions'
485
469
  && typeof this.prepareSessionTabRender === 'function'
@@ -488,7 +472,7 @@
488
472
  this.prepareSessionTabRender();
489
473
  }
490
474
  this.scheduleAfterFrame(() => {
491
- this.clearMainTabSwitchIntent(normalizedTab);
475
+ this.clearMainTabSwitchIntent(targetTab);
492
476
  });
493
477
  return;
494
478
  }
@@ -510,7 +494,7 @@
510
494
  void loadDoctorOverview(this);
511
495
  }
512
496
  this.scheduleAfterFrame(() => {
513
- this.clearMainTabSwitchIntent(normalizedTab);
497
+ this.clearMainTabSwitchIntent(targetTab);
514
498
  });
515
499
  return result;
516
500
  }
@@ -527,7 +511,7 @@
527
511
  if (pendingTarget === 'dashboard') {
528
512
  void loadDoctorOverview(this);
529
513
  }
530
- this.clearMainTabSwitchIntent(normalizedTab);
514
+ this.clearMainTabSwitchIntent(pendingTarget);
531
515
  });
532
516
  },
533
517
 
@@ -360,13 +360,10 @@ export function createOpenclawEditingMethods() {
360
360
  },
361
361
 
362
362
  saveOpenclawConfigs() {
363
- try {
364
- localStorage.setItem('openclawConfigs', JSON.stringify(this.openclawConfigs));
365
- return true;
366
- } catch (_) {
367
- this.showMessage('保存本地 OpenClaw 配置失败', 'error');
368
- return false;
363
+ if (typeof this.persistWebUiPreferences === 'function') {
364
+ this.persistWebUiPreferences({ openclawConfigs: this.openclawConfigs });
369
365
  }
366
+ return true;
370
367
  },
371
368
 
372
369
  // Accordion stepper methods
@@ -1,6 +1,5 @@
1
1
  import {
2
- normalizeConfigTemplateDiffConfirmEnabled,
3
- persistConfigTemplateDiffConfirmEnabledToStorage
2
+ normalizeConfigTemplateDiffConfirmEnabled
4
3
  } from './config-template-confirm-pref.mjs';
5
4
 
6
5
  export function createSessionActionMethods(options = {}) {
@@ -302,9 +301,6 @@ export function createSessionActionMethods(options = {}) {
302
301
  setSessionTrashEnabled(value) {
303
302
  const enabled = this.normalizeSessionTrashEnabled(value);
304
303
  this.sessionTrashEnabled = enabled;
305
- try {
306
- localStorage.setItem('codexmateSessionTrashEnabled', enabled ? 'true' : 'false');
307
- } catch (_) {}
308
304
  if (typeof this.persistWebUiPreferences === 'function') {
309
305
  this.persistWebUiPreferences({ sessionTrashEnabled: enabled });
310
306
  }
@@ -313,9 +309,6 @@ export function createSessionActionMethods(options = {}) {
313
309
  setSessionTimelineStyle(style) {
314
310
  const normalized = style === 'bar' ? 'bar' : 'dots';
315
311
  this.sessionTimelineStyle = normalized;
316
- try {
317
- localStorage.setItem('codexmateSessionTimelineStyle', normalized);
318
- } catch (_) {}
319
312
  if (typeof this.persistWebUiPreferences === 'function') {
320
313
  this.persistWebUiPreferences({ sessionTimelineStyle: normalized });
321
314
  }
@@ -324,7 +317,6 @@ export function createSessionActionMethods(options = {}) {
324
317
  setConfigTemplateDiffConfirmEnabled(value) {
325
318
  const enabled = this.normalizeConfigTemplateDiffConfirmEnabled(value);
326
319
  this.configTemplateDiffConfirmEnabled = enabled;
327
- persistConfigTemplateDiffConfirmEnabledToStorage(enabled);
328
320
  if (typeof this.persistWebUiPreferences === 'function') {
329
321
  this.persistWebUiPreferences({ configTemplateDiffConfirmEnabled: enabled });
330
322
  }
@@ -338,9 +330,6 @@ export function createSessionActionMethods(options = {}) {
338
330
  setShareCommandPrefix(value) {
339
331
  const normalized = this.normalizeShareCommandPrefix(value);
340
332
  this.shareCommandPrefix = normalized;
341
- try {
342
- localStorage.setItem('codexmateShareCommandPrefix', normalized);
343
- } catch (_) {}
344
333
  if (typeof this.persistWebUiPreferences === 'function') {
345
334
  this.persistWebUiPreferences({ shareCommandPrefix: normalized });
346
335
  }
@@ -30,16 +30,7 @@ function isSessionLoadNativeDialogEnabled(vm) {
30
30
  // ignore global flag lookup failures
31
31
  }
32
32
 
33
- try {
34
- if (typeof localStorage !== 'undefined' && typeof localStorage.getItem === 'function') {
35
- const stored = String(localStorage.getItem('codexmateSessionLoadNativeDialog') || '').trim().toLowerCase();
36
- if (stored === '1' || stored === 'true' || stored === 'yes' || stored === 'on') {
37
- return true;
38
- }
39
- }
40
- } catch (_) {
41
- // ignore storage lookup failures
42
- }
33
+ if (vm && vm.sessionLoadNativeDialog === true) return true;
43
34
 
44
35
  try {
45
36
  const search = typeof location !== 'undefined' && location && typeof location.search === 'string'
@@ -271,28 +262,17 @@ export function createSessionBrowserMethods(options = {}) {
271
262
  url.hash = '';
272
263
  window.history.replaceState(null, '', url.toString());
273
264
  } catch (_) {}
274
- try {
275
- const sortCache = localStorage.getItem('codexmateSessionSortMode');
276
- this.sessionSortMode = normalizeSortMode(sortCache);
277
- } catch (_) {}
278
265
  if (this.mainTab === 'sessions' && typeof this.loadSessions === 'function') {
279
266
  void this.loadSessions();
280
267
  }
281
268
  return;
282
269
  }
283
- const sourceCache = localStorage.getItem('codexmateSessionFilterSource');
284
- const pathCache = localStorage.getItem('codexmateSessionPathFilter');
285
- const cached = buildSessionFilterCacheState(sourceCache, pathCache);
270
+ const cached = buildSessionFilterCacheState(this.sessionFilterSource, this.sessionPathFilter);
286
271
  this.sessionFilterSource = cached.source;
287
272
  this.sessionPathFilter = cached.pathFilter;
288
- const queryCache = localStorage.getItem('codexmateSessionQuery');
289
- const roleCache = localStorage.getItem('codexmateSessionRoleFilter');
290
- const timeCache = localStorage.getItem('codexmateSessionTimePreset');
291
- const sortCache = localStorage.getItem('codexmateSessionSortMode');
292
- this.sessionQuery = typeof queryCache === 'string' ? queryCache : '';
293
- this.sessionRoleFilter = normalizeSessionRoleFilter(roleCache);
294
- this.sessionTimePreset = normalizeSessionTimePreset(timeCache);
295
- this.sessionSortMode = normalizeSortMode(sortCache);
273
+ this.sessionRoleFilter = normalizeSessionRoleFilter(this.sessionRoleFilter);
274
+ this.sessionTimePreset = normalizeSessionTimePreset(this.sessionTimePreset);
275
+ this.sessionSortMode = normalizeSortMode(this.sessionSortMode);
296
276
  this.refreshSessionPathOptions(this.sessionFilterSource);
297
277
  if (this.mainTab === 'sessions' && typeof this.loadSessions === 'function') {
298
278
  const shouldReload = cached.source !== 'all'
@@ -308,27 +288,26 @@ export function createSessionBrowserMethods(options = {}) {
308
288
 
309
289
  persistSessionFilterCache() {
310
290
  const cached = buildSessionFilterCacheState(this.sessionFilterSource, this.sessionPathFilter);
311
- localStorage.setItem('codexmateSessionFilterSource', cached.source);
312
- if (cached.pathFilter) {
313
- localStorage.setItem('codexmateSessionPathFilter', cached.pathFilter);
314
- } else {
315
- localStorage.removeItem('codexmateSessionPathFilter');
316
- }
317
- if (this.sessionQuery && isSessionQueryEnabled(this.sessionFilterSource)) {
318
- localStorage.setItem('codexmateSessionQuery', this.sessionQuery);
319
- } else {
320
- localStorage.removeItem('codexmateSessionQuery');
291
+ if (typeof this.persistWebUiPreferences === 'function') {
292
+ this.persistWebUiPreferences({
293
+ sessionFilters: {
294
+ source: cached.source,
295
+ pathFilter: cached.pathFilter,
296
+ query: this.sessionQuery && isSessionQueryEnabled(this.sessionFilterSource) ? this.sessionQuery : '',
297
+ roleFilter: normalizeSessionRoleFilter(this.sessionRoleFilter),
298
+ timePreset: normalizeSessionTimePreset(this.sessionTimePreset),
299
+ sortMode: this.normalizeSessionSortMode(this.sessionSortMode)
300
+ }
301
+ });
321
302
  }
322
- localStorage.setItem('codexmateSessionRoleFilter', normalizeSessionRoleFilter(this.sessionRoleFilter));
323
- localStorage.setItem('codexmateSessionTimePreset', normalizeSessionTimePreset(this.sessionTimePreset));
324
303
  },
325
304
 
326
305
  onSessionSortChange() {
327
306
  const normalized = this.normalizeSessionSortMode(this.sessionSortMode);
328
307
  this.sessionSortMode = normalized;
329
- try {
330
- localStorage.setItem('codexmateSessionSortMode', normalized);
331
- } catch (_) {}
308
+ if (typeof this.persistWebUiPreferences === 'function') {
309
+ this.persistWebUiPreferences({ sessionFilters: { sortMode: normalized } });
310
+ }
332
311
  },
333
312
 
334
313
  getSessionHotLabel(session) {
@@ -361,25 +340,16 @@ export function createSessionBrowserMethods(options = {}) {
361
340
  },
362
341
 
363
342
  restoreSessionPinnedMap() {
364
- const cached = localStorage.getItem('codexmateSessionPinnedMap');
365
- if (!cached) {
366
- this.sessionPinnedMap = {};
367
- return;
368
- }
369
- try {
370
- const parsed = JSON.parse(cached);
371
- this.sessionPinnedMap = this.normalizeSessionPinnedMap(parsed);
372
- } catch (_) {
373
- this.sessionPinnedMap = {};
374
- localStorage.removeItem('codexmateSessionPinnedMap');
375
- }
343
+ this.sessionPinnedMap = this.normalizeSessionPinnedMap(this.sessionPinnedMap);
376
344
  },
377
345
 
378
346
  persistSessionPinnedMap() {
379
347
  const payload = (this.sessionPinnedMap && typeof this.sessionPinnedMap === 'object')
380
348
  ? this.sessionPinnedMap
381
349
  : {};
382
- localStorage.setItem('codexmateSessionPinnedMap', JSON.stringify(payload));
350
+ if (typeof this.persistWebUiPreferences === 'function') {
351
+ this.persistWebUiPreferences({ sessionPinnedMap: payload });
352
+ }
383
353
  },
384
354
 
385
355
  shouldPruneSessionPinnedMap(sessions = this.sessionsList) {
@@ -802,7 +772,6 @@ export function createSessionBrowserMethods(options = {}) {
802
772
  const normalized = typeof nextRange === 'string' ? nextRange.trim().toLowerCase() : '';
803
773
  const range = normalized === 'all' ? 'all' : (normalized === '30d' ? '30d' : '7d');
804
774
  this.sessionsUsageTimeRange = range;
805
- try { localStorage.setItem('sessionsUsageTimeRange', range); } catch (_) {}
806
775
  if (typeof this.persistWebUiPreferences === 'function') {
807
776
  this.persistWebUiPreferences({ sessionsUsageTimeRange: range });
808
777
  }
@@ -310,7 +310,6 @@ export function createSessionTrashMethods(options = {}) {
310
310
  setSessionTrashRetentionDays(days) {
311
311
  const normalized = this.normalizeSessionTrashRetentionDays(days);
312
312
  this.sessionTrashRetentionDays = normalized;
313
- try { localStorage.setItem('codexmateSessionTrashRetentionDays', String(normalized)); } catch (_) {}
314
313
  if (typeof this.persistWebUiPreferences === 'function') {
315
314
  this.persistWebUiPreferences({ sessionTrashRetentionDays: normalized });
316
315
  }
@@ -10,28 +10,20 @@ import {
10
10
  } from '../logic.mjs';
11
11
  import { nextClaudeConfigName } from './provider-default-names.mjs';
12
12
 
13
- const DELETED_CLAUDE_SETTINGS_IMPORTS_STORAGE_KEY = 'deletedClaudeSettingsImports';
14
-
15
13
  function normalizeDeletedClaudeImportUrl(value) {
16
14
  return typeof value === 'string' ? value.trim().replace(/\/+$/g, '') : '';
17
15
  }
18
16
 
19
- function shouldSuppressDeletedClaudeSettingsImport(env = {}) {
17
+ function shouldSuppressDeletedClaudeSettingsImport(env = {}, deletedEntries = []) {
20
18
  const normalized = normalizeClaudeSettingsEnv(env);
21
19
  const baseUrl = normalizeDeletedClaudeImportUrl(normalized.baseUrl);
22
20
  const model = normalizeClaudeValue(normalized.model);
23
21
  if (!baseUrl || !model) return false;
24
- try {
25
- const raw = localStorage.getItem(DELETED_CLAUDE_SETTINGS_IMPORTS_STORAGE_KEY);
26
- const parsed = raw ? JSON.parse(raw) : [];
27
- const entries = Array.isArray(parsed) ? parsed : [];
28
- return entries.some((entry) => entry
29
- && typeof entry === 'object'
30
- && normalizeDeletedClaudeImportUrl(entry.baseUrl) === baseUrl
31
- && normalizeClaudeValue(entry.model) === model);
32
- } catch (_) {
33
- return false;
34
- }
22
+ const entries = Array.isArray(deletedEntries) ? deletedEntries : [];
23
+ return entries.some((entry) => entry
24
+ && typeof entry === 'object'
25
+ && normalizeDeletedClaudeImportUrl(entry.baseUrl) === baseUrl
26
+ && normalizeClaudeValue(entry.model) === model);
35
27
  }
36
28
 
37
29
  export function createStartupClaudeMethods(options = {}) {
@@ -153,9 +145,6 @@ export function createStartupClaudeMethods(options = {}) {
153
145
  claude: statusRes.toolConfigPermissions.claude === true,
154
146
  opencode: statusRes.toolConfigPermissions.opencode === true
155
147
  };
156
- try {
157
- localStorage.setItem('toolConfigPermissions', JSON.stringify(this.toolConfigPermissions));
158
- } catch (_) {}
159
148
  }
160
149
  this.providersList = listRes.providers;
161
150
  if (typeof this.loadLocalBridgeExcluded === 'function') { this.loadLocalBridgeExcluded(); }
@@ -275,7 +264,7 @@ export function createStartupClaudeMethods(options = {}) {
275
264
  },
276
265
 
277
266
  shouldSuppressClaudeSettingsImport(env) {
278
- return isLikelyBuiltinClaudeProxySettingsEnv(env) || shouldSuppressDeletedClaudeSettingsImport(env);
267
+ return isLikelyBuiltinClaudeProxySettingsEnv(env) || shouldSuppressDeletedClaudeSettingsImport(env, this.deletedClaudeSettingsImports);
279
268
  },
280
269
 
281
270
  findDuplicateClaudeConfigName(config) {
@@ -381,7 +370,7 @@ export function createStartupClaudeMethods(options = {}) {
381
370
  if (matchName) {
382
371
  if (this.currentClaudeConfig !== matchName) {
383
372
  this.currentClaudeConfig = matchName;
384
- try { localStorage.setItem('currentClaudeConfig', matchName); } catch (_) {}
373
+ if (typeof this.persistWebUiPreferences === 'function') this.persistWebUiPreferences({ currentClaudeConfig: matchName });
385
374
  }
386
375
  this.refreshClaudeModelContext({ silentError: silentModelError });
387
376
  return;
@@ -390,7 +379,7 @@ export function createStartupClaudeMethods(options = {}) {
390
379
  if (builtinProxyMatch) {
391
380
  if (this.currentClaudeConfig !== builtinProxyMatch) {
392
381
  this.currentClaudeConfig = builtinProxyMatch;
393
- try { localStorage.setItem('currentClaudeConfig', builtinProxyMatch); } catch (_) {}
382
+ if (typeof this.persistWebUiPreferences === 'function') this.persistWebUiPreferences({ currentClaudeConfig: builtinProxyMatch });
394
383
  }
395
384
  this.refreshClaudeModelContext({ silentError: silentModelError });
396
385
  return;
@@ -401,7 +390,7 @@ export function createStartupClaudeMethods(options = {}) {
401
390
  if (importedName) {
402
391
  if (this.currentClaudeConfig !== importedName) {
403
392
  this.currentClaudeConfig = importedName;
404
- try { localStorage.setItem('currentClaudeConfig', importedName); } catch (_) {}
393
+ if (typeof this.persistWebUiPreferences === 'function') this.persistWebUiPreferences({ currentClaudeConfig: importedName });
405
394
  }
406
395
  this.refreshClaudeModelContext({ silentError: silentModelError });
407
396
  if (!silent) {
@@ -417,14 +406,14 @@ export function createStartupClaudeMethods(options = {}) {
417
406
  : (configNames[0] || '');
418
407
  if (!fallback) {
419
408
  this.currentClaudeConfig = '';
420
- try { localStorage.setItem('currentClaudeConfig', ''); } catch (_) {}
409
+ if (typeof this.persistWebUiPreferences === 'function') this.persistWebUiPreferences({ currentClaudeConfig: '' });
421
410
  this.currentClaudeModel = '';
422
411
  this.resetClaudeModelsState();
423
412
  return;
424
413
  }
425
414
  if (this.currentClaudeConfig !== fallback) {
426
415
  this.currentClaudeConfig = fallback;
427
- try { localStorage.setItem('currentClaudeConfig', fallback); } catch (_) {}
416
+ if (typeof this.persistWebUiPreferences === 'function') this.persistWebUiPreferences({ currentClaudeConfig: fallback });
428
417
  }
429
418
  this.refreshClaudeModelContext({ silentError: silentModelError });
430
419
  }
@@ -611,14 +600,10 @@ export function createStartupClaudeMethods(options = {}) {
611
600
  },
612
601
 
613
602
  maybeShowStarPrompt() {
614
- const storageKey = 'codexmateStarPrompted';
615
- try {
616
- if (!localStorage.getItem(storageKey)) {
617
- localStorage.setItem(storageKey, '1');
618
- }
619
- } catch (_) {
620
- // Ignore storage failures silently. The startup UI should not show
621
- // promotional prompts or block normal configuration work.
603
+ if (this.starPrompted === true) return;
604
+ this.starPrompted = true;
605
+ if (typeof this.persistWebUiPreferences === 'function') {
606
+ this.persistWebUiPreferences({ starPrompted: true });
622
607
  }
623
608
  }
624
609
  };