codexmate 0.0.55 → 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 (49) hide show
  1. package/README.md +2 -0
  2. package/README.vi.md +2 -0
  3. package/README.zh.md +2 -0
  4. package/cli/local-bridge.js +221 -22
  5. package/cli.js +797 -134
  6. package/lib/task-orchestrator.js +90 -21
  7. package/lib/task-workspace-chat.js +292 -0
  8. package/package.json +2 -2
  9. package/web-ui/app.js +57 -129
  10. package/web-ui/modules/app.computed.main-tabs.mjs +304 -7
  11. package/web-ui/modules/app.computed.session.mjs +210 -0
  12. package/web-ui/modules/app.methods.agents.mjs +3 -0
  13. package/web-ui/modules/app.methods.claude-config.mjs +178 -22
  14. package/web-ui/modules/app.methods.codex-config.mjs +294 -65
  15. package/web-ui/modules/app.methods.navigation.mjs +71 -84
  16. package/web-ui/modules/app.methods.openclaw-editing.mjs +3 -6
  17. package/web-ui/modules/app.methods.providers.mjs +15 -1
  18. package/web-ui/modules/app.methods.runtime.mjs +7 -2
  19. package/web-ui/modules/app.methods.session-actions.mjs +25 -12
  20. package/web-ui/modules/app.methods.session-browser.mjs +23 -54
  21. package/web-ui/modules/app.methods.session-trash.mjs +0 -1
  22. package/web-ui/modules/app.methods.startup-claude.mjs +35 -17
  23. package/web-ui/modules/app.methods.task-orchestration.mjs +347 -8
  24. package/web-ui/modules/app.methods.tool-config-permissions.mjs +0 -3
  25. package/web-ui/modules/app.methods.web-ui-preferences.mjs +415 -68
  26. package/web-ui/modules/config-template-confirm-pref.mjs +2 -3
  27. package/web-ui/modules/i18n/locales/en.mjs +187 -28
  28. package/web-ui/modules/i18n/locales/ja.mjs +184 -25
  29. package/web-ui/modules/i18n/locales/vi.mjs +186 -33
  30. package/web-ui/modules/i18n/locales/zh-tw.mjs +187 -28
  31. package/web-ui/modules/i18n/locales/zh.mjs +189 -30
  32. package/web-ui/modules/i18n.mjs +5 -12
  33. package/web-ui/modules/provider-default-names.mjs +25 -0
  34. package/web-ui/modules/sessions-filters-url.mjs +1 -2
  35. package/web-ui/partials/index/layout-header.html +37 -14
  36. package/web-ui/partials/index/modal-health-check.html +69 -5
  37. package/web-ui/partials/index/panel-config-codex.html +2 -2
  38. package/web-ui/partials/index/panel-orchestration.html +489 -282
  39. package/web-ui/partials/index/panel-sessions.html +97 -17
  40. package/web-ui/res/web-ui-render.precompiled.js +1423 -732
  41. package/web-ui/session-helpers.mjs +4 -1
  42. package/web-ui/styles/layout-shell.css +157 -1
  43. package/web-ui/styles/navigation-panels.css +11 -0
  44. package/web-ui/styles/responsive.css +98 -0
  45. package/web-ui/styles/sessions-preview.css +212 -2
  46. package/web-ui/styles/sessions-toolbar-trash.css +61 -18
  47. package/web-ui/styles/skills-list.css +122 -0
  48. package/web-ui/styles/task-orchestration.css +2161 -4
  49. package/web-ui/styles/titles-cards.css +52 -0
@@ -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
 
@@ -724,7 +708,10 @@
724
708
  ? this.activeSessionMessages.length
725
709
  : 0;
726
710
  if (total <= 0) return;
727
- this.sessionPreviewVisibleCount = total;
711
+ const initialBatchSize = Number.isFinite(this.sessionPreviewInitialBatchSize)
712
+ ? Math.max(1, Math.floor(this.sessionPreviewInitialBatchSize))
713
+ : 12;
714
+ this.sessionPreviewVisibleCount = Math.min(total, initialBatchSize);
728
715
  this.invalidateSessionTimelineMeasurementCache();
729
716
  },
730
717
 
@@ -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,3 +1,5 @@
1
+ import { nextCodexProviderName } from './provider-default-names.mjs';
2
+
1
3
  const PROVIDER_NAME_PATTERN = /^[a-zA-Z0-9._-]+$/;
2
4
  const RESERVED_PROXY_PROVIDER_NAME = 'codexmate-proxy';
3
5
  const RESERVED_LOCAL_PROVIDER_NAME = 'local';
@@ -327,6 +329,18 @@ export function createProvidersMethods(options = {}) {
327
329
  this.showAddModal = true;
328
330
  },
329
331
 
332
+ openAddProviderModal() {
333
+ this.newProvider = {
334
+ name: nextCodexProviderName(this.providersList),
335
+ url: '',
336
+ key: '',
337
+ model: '',
338
+ useTransform: false
339
+ };
340
+ this.showAddProviderKey = false;
341
+ this.showAddModal = true;
342
+ },
343
+
330
344
  async openEditModal(provider) {
331
345
  const requestId = Symbol('openEditModal');
332
346
  this._openEditModalRequestId = requestId;
@@ -536,7 +550,7 @@ export function createProvidersMethods(options = {}) {
536
550
  closeAddModal() {
537
551
  this.showAddModal = false;
538
552
  this.showAddProviderKey = false;
539
- this.newProvider = { name: '', url: '', key: '', model: '', useTransform: false };
553
+ this.newProvider = { name: nextCodexProviderName(this.providersList), url: '', key: '', model: '', useTransform: false };
540
554
  },
541
555
 
542
556
  toggleAddProviderKey() {
@@ -154,6 +154,10 @@ export function createRuntimeMethods(options = {}) {
154
154
  const baseUrl = config && typeof config.baseUrl === 'string' ? config.baseUrl.trim() : '';
155
155
  const apiKey = config && typeof config.apiKey === 'string' ? config.apiKey.trim() : '';
156
156
  const model = config && typeof config.model === 'string' ? config.model.trim() : '';
157
+ const targetApiRaw = config && typeof config.targetApi === 'string' ? config.targetApi.trim().toLowerCase() : '';
158
+ const targetApi = targetApiRaw === 'ollama'
159
+ ? 'ollama'
160
+ : (targetApiRaw === 'chat_completions' || targetApiRaw === 'chat-completions' || targetApiRaw === 'chat/completions' ? 'chat_completions' : 'responses');
157
161
  this.claudeSpeedLoading[name] = true;
158
162
  try {
159
163
  if (!baseUrl) {
@@ -161,7 +165,7 @@ export function createRuntimeMethods(options = {}) {
161
165
  this.claudeSpeedResults[name] = res;
162
166
  return res;
163
167
  }
164
- if (!apiKey) {
168
+ if (!apiKey && targetApi !== 'ollama') {
165
169
  const res = { ok: false, error: 'Missing API key' };
166
170
  this.claudeSpeedResults[name] = res;
167
171
  return res;
@@ -175,7 +179,8 @@ export function createRuntimeMethods(options = {}) {
175
179
  kind: 'claude',
176
180
  url: baseUrl,
177
181
  apiKey,
178
- model
182
+ model,
183
+ targetApi
179
184
  });
180
185
  if (res.error) {
181
186
  this.claudeSpeedResults[name] = { ok: false, error: res.error };
@@ -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 = {}) {
@@ -165,6 +164,30 @@ export function createSessionActionMethods(options = {}) {
165
164
  this.showMessage(this.t('toast.copy.fail'), 'error');
166
165
  },
167
166
 
167
+ async copySessionWorkspaceBrief() {
168
+ const summary = this.activeSessionWorkspaceSummary;
169
+ const text = summary && typeof summary.briefText === 'string'
170
+ ? summary.briefText.trim()
171
+ : '';
172
+ if (!text) {
173
+ this.showMessage(this.t('sessions.workspace.copy.empty'), 'error');
174
+ return;
175
+ }
176
+ const ok = this.fallbackCopyText(text);
177
+ if (ok) {
178
+ this.showMessage(this.t('sessions.workspace.copy.success'), 'success');
179
+ return;
180
+ }
181
+ try {
182
+ if (navigator.clipboard && window.isSecureContext) {
183
+ await navigator.clipboard.writeText(text);
184
+ this.showMessage(this.t('sessions.workspace.copy.success'), 'success');
185
+ return;
186
+ }
187
+ } catch (_) {}
188
+ this.showMessage(this.t('toast.copy.fail'), 'error');
189
+ },
190
+
168
191
  getSessionExportKey(session) {
169
192
  return `${session.source || 'unknown'}:${session.sessionId || ''}:${session.filePath || ''}`;
170
193
  },
@@ -278,9 +301,6 @@ export function createSessionActionMethods(options = {}) {
278
301
  setSessionTrashEnabled(value) {
279
302
  const enabled = this.normalizeSessionTrashEnabled(value);
280
303
  this.sessionTrashEnabled = enabled;
281
- try {
282
- localStorage.setItem('codexmateSessionTrashEnabled', enabled ? 'true' : 'false');
283
- } catch (_) {}
284
304
  if (typeof this.persistWebUiPreferences === 'function') {
285
305
  this.persistWebUiPreferences({ sessionTrashEnabled: enabled });
286
306
  }
@@ -289,9 +309,6 @@ export function createSessionActionMethods(options = {}) {
289
309
  setSessionTimelineStyle(style) {
290
310
  const normalized = style === 'bar' ? 'bar' : 'dots';
291
311
  this.sessionTimelineStyle = normalized;
292
- try {
293
- localStorage.setItem('codexmateSessionTimelineStyle', normalized);
294
- } catch (_) {}
295
312
  if (typeof this.persistWebUiPreferences === 'function') {
296
313
  this.persistWebUiPreferences({ sessionTimelineStyle: normalized });
297
314
  }
@@ -300,7 +317,6 @@ export function createSessionActionMethods(options = {}) {
300
317
  setConfigTemplateDiffConfirmEnabled(value) {
301
318
  const enabled = this.normalizeConfigTemplateDiffConfirmEnabled(value);
302
319
  this.configTemplateDiffConfirmEnabled = enabled;
303
- persistConfigTemplateDiffConfirmEnabledToStorage(enabled);
304
320
  if (typeof this.persistWebUiPreferences === 'function') {
305
321
  this.persistWebUiPreferences({ configTemplateDiffConfirmEnabled: enabled });
306
322
  }
@@ -314,9 +330,6 @@ export function createSessionActionMethods(options = {}) {
314
330
  setShareCommandPrefix(value) {
315
331
  const normalized = this.normalizeShareCommandPrefix(value);
316
332
  this.shareCommandPrefix = normalized;
317
- try {
318
- localStorage.setItem('codexmateShareCommandPrefix', normalized);
319
- } catch (_) {}
320
333
  if (typeof this.persistWebUiPreferences === 'function') {
321
334
  this.persistWebUiPreferences({ shareCommandPrefix: normalized });
322
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
  }