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
@@ -1,79 +1,339 @@
1
- export function createWebUiPreferencesMethods(options = {}) {
2
- const api = typeof options.api === 'function' ? options.api : async () => ({});
3
- const storageOverride = options.storage && typeof options.storage === 'object' ? options.storage : null;
1
+ const LEGACY_WEB_UI_PREFERENCE_KEYS = Object.freeze([
2
+ 'codexmateShareCommandPrefix',
3
+ 'codexmateSessionTrashEnabled',
4
+ 'codexmateSessionTrashRetentionDays',
5
+ 'codexmateSessionTimelineStyle',
6
+ 'codexmateConfigTemplateDiffConfirmEnabled',
7
+ 'sessionsUsageTimeRange',
8
+ 'codexmate_prompts_sub_tab',
9
+ 'codexmate_project_claude_md_path',
10
+ 'codexmateNavState.v1',
11
+ 'codexmateTaskOrchestrationTabEnabled',
12
+ 'codexmateSessionLoadNativeDialog',
13
+ 'codexmateSessionFilterSource',
14
+ 'codexmateSessionPathFilter',
15
+ 'codexmateSessionQuery',
16
+ 'codexmateSessionRoleFilter',
17
+ 'codexmateSessionTimePreset',
18
+ 'codexmateSessionSortMode',
19
+ 'codexmateSessionPinnedMap',
20
+ 'claudeConfigs',
21
+ 'currentClaudeConfig',
22
+ 'openclawConfigs',
23
+ 'toolConfigPermissions',
24
+ 'deletedClaudeSettingsImports',
25
+ 'codexmateLang',
26
+ 'codexmateSidebarCollapsed',
27
+ 'codexmateStarPrompted'
28
+ ]);
4
29
 
5
- const getLocalStorage = () => {
6
- const storage = storageOverride || (typeof globalThis !== 'undefined' ? globalThis.localStorage : null);
7
- return storage && typeof storage.setItem === 'function' && typeof storage.removeItem === 'function'
8
- ? storage
9
- : null;
30
+ function hasOwn(source, key) {
31
+ return !!source && Object.prototype.hasOwnProperty.call(source, key);
32
+ }
33
+
34
+ function isPlainObject(value) {
35
+ return !!value && typeof value === 'object' && !Array.isArray(value);
36
+ }
37
+
38
+ function parseJsonValue(raw, fallback) {
39
+ if (typeof raw !== 'string' || !raw.trim()) return fallback;
40
+ try {
41
+ const parsed = JSON.parse(raw);
42
+ return parsed === undefined ? fallback : parsed;
43
+ } catch (_) {
44
+ return fallback;
45
+ }
46
+ }
47
+
48
+ function normalizeBoolean(value, defaultValue = true) {
49
+ if (value === true) return true;
50
+ if (value === false) return false;
51
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
52
+ if (normalized === '1' || normalized === 'true' || normalized === 'on' || normalized === 'yes') return true;
53
+ if (normalized === '0' || normalized === 'false' || normalized === 'off' || normalized === 'no') return false;
54
+ return defaultValue !== false;
55
+ }
56
+
57
+ function normalizeUsageTimeRange(value) {
58
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
59
+ if (normalized === 'all' || normalized === '30d') return normalized;
60
+ return '7d';
61
+ }
62
+
63
+ function normalizePromptsSubTab(value) {
64
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
65
+ return normalized === 'claude-project' ? 'claude-project' : 'codex';
66
+ }
67
+
68
+ function normalizeSessionSortMode(value) {
69
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
70
+ return normalized === 'hot' ? 'hot' : 'time';
71
+ }
72
+
73
+ function normalizeNavigationSnapshot(vm, source = {}) {
74
+ const currentSkillsTargetApp = vm.skillsTargetApp === 'claude' ? 'claude' : 'codex';
75
+ const currentPromptTemplatesMode = vm.promptTemplatesMode === 'manage' ? 'manage' : 'compose';
76
+ return {
77
+ mainTab: typeof source.mainTab === 'string' ? source.mainTab : vm.mainTab,
78
+ configMode: typeof source.configMode === 'string' ? source.configMode : vm.configMode,
79
+ settingsTab: typeof source.settingsTab === 'string' ? source.settingsTab : vm.settingsTab,
80
+ skillsTargetApp: source.skillsTargetApp === 'claude' || source.skillsTargetApp === 'codex'
81
+ ? source.skillsTargetApp
82
+ : currentSkillsTargetApp,
83
+ promptTemplatesMode: source.promptTemplatesMode === 'manage' || source.promptTemplatesMode === 'compose'
84
+ ? source.promptTemplatesMode
85
+ : currentPromptTemplatesMode
10
86
  };
87
+ }
11
88
 
12
- const setLocalStorageValue = (key, value) => {
13
- const storage = getLocalStorage();
14
- if (!storage) return;
15
- try {
16
- if (value === null || value === undefined || value === '') {
17
- storage.removeItem(key);
18
- } else {
19
- storage.setItem(key, String(value));
20
- }
21
- } catch (_) {}
89
+ function normalizeSessionFiltersSnapshot(vm, source = {}) {
90
+ const incoming = isPlainObject(source) ? source : {};
91
+ const normalizeRole = typeof vm.normalizeSessionRoleFilter === 'function'
92
+ ? vm.normalizeSessionRoleFilter.bind(vm)
93
+ : ((value) => {
94
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
95
+ return ['user', 'assistant', 'system', 'tool'].includes(normalized) ? normalized : 'all';
96
+ });
97
+ const normalizeTime = typeof vm.normalizeSessionTimePreset === 'function'
98
+ ? vm.normalizeSessionTimePreset.bind(vm)
99
+ : ((value) => {
100
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
101
+ return ['24h', '7d', '30d'].includes(normalized) ? normalized : 'all';
102
+ });
103
+ return {
104
+ source: typeof incoming.source === 'string' ? incoming.source : vm.sessionFilterSource,
105
+ pathFilter: typeof incoming.pathFilter === 'string' ? incoming.pathFilter : (vm.sessionPathFilter || ''),
106
+ query: typeof incoming.query === 'string' ? incoming.query : (vm.sessionQuery || ''),
107
+ roleFilter: normalizeRole(hasOwn(incoming, 'roleFilter') ? incoming.roleFilter : vm.sessionRoleFilter),
108
+ timePreset: normalizeTime(hasOwn(incoming, 'timePreset') ? incoming.timePreset : vm.sessionTimePreset),
109
+ sortMode: typeof vm.normalizeSessionSortMode === 'function'
110
+ ? vm.normalizeSessionSortMode(hasOwn(incoming, 'sortMode') ? incoming.sortMode : vm.sessionSortMode)
111
+ : normalizeSessionSortMode(hasOwn(incoming, 'sortMode') ? incoming.sortMode : vm.sessionSortMode)
22
112
  };
113
+ }
23
114
 
24
- const normalizeUsageTimeRange = (value) => {
25
- const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
26
- if (normalized === 'all' || normalized === '30d') return normalized;
27
- return '7d';
115
+ function normalizePinnedMap(vm, value) {
116
+ if (typeof vm.normalizeSessionPinnedMap === 'function') {
117
+ return vm.normalizeSessionPinnedMap(value);
118
+ }
119
+ const source = isPlainObject(value) ? value : {};
120
+ const next = {};
121
+ for (const [key, item] of Object.entries(source)) {
122
+ if (!key) continue;
123
+ const numeric = Number(item);
124
+ if (!Number.isFinite(numeric) || numeric <= 0) continue;
125
+ next[key] = Math.floor(numeric);
126
+ }
127
+ return next;
128
+ }
129
+
130
+ function readStorageValue(storage, key) {
131
+ if (!storage || typeof storage.getItem !== 'function') return null;
132
+ try {
133
+ return storage.getItem(key);
134
+ } catch (_) {
135
+ return null;
136
+ }
137
+ }
138
+
139
+ function collectLegacyLocalStoragePreferences(storage) {
140
+ const read = (key) => readStorageValue(storage, key);
141
+ const has = (key) => read(key) !== null;
142
+ const preferences = {};
143
+ let found = false;
144
+
145
+ const assignString = (targetKey, storageKey) => {
146
+ const value = read(storageKey);
147
+ if (typeof value === 'string') {
148
+ preferences[targetKey] = value;
149
+ found = true;
150
+ }
28
151
  };
29
152
 
30
- const normalizePromptsSubTab = (value) => {
31
- const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
32
- return normalized === 'claude-project' ? 'claude-project' : 'codex';
153
+ assignString('shareCommandPrefix', 'codexmateShareCommandPrefix');
154
+ assignString('sessionTrashRetentionDays', 'codexmateSessionTrashRetentionDays');
155
+ assignString('sessionTimelineStyle', 'codexmateSessionTimelineStyle');
156
+ assignString('sessionsUsageTimeRange', 'sessionsUsageTimeRange');
157
+ assignString('promptsSubTab', 'codexmate_prompts_sub_tab');
158
+ assignString('projectClaudeMdPath', 'codexmate_project_claude_md_path');
159
+ assignString('language', 'codexmateLang');
160
+ assignString('currentClaudeConfig', 'currentClaudeConfig');
161
+
162
+ if (has('codexmateSessionTrashEnabled')) {
163
+ preferences.sessionTrashEnabled = normalizeBoolean(read('codexmateSessionTrashEnabled'), true);
164
+ found = true;
165
+ }
166
+ if (has('codexmateConfigTemplateDiffConfirmEnabled')) {
167
+ preferences.configTemplateDiffConfirmEnabled = normalizeBoolean(read('codexmateConfigTemplateDiffConfirmEnabled'), true);
168
+ found = true;
169
+ }
170
+ if (has('codexmateTaskOrchestrationTabEnabled')) {
171
+ preferences.taskOrchestrationTabEnabled = normalizeBoolean(read('codexmateTaskOrchestrationTabEnabled'), true);
172
+ found = true;
173
+ }
174
+ if (has('codexmateSessionLoadNativeDialog')) {
175
+ preferences.sessionLoadNativeDialog = normalizeBoolean(read('codexmateSessionLoadNativeDialog'), false);
176
+ found = true;
177
+ }
178
+ if (has('codexmateSidebarCollapsed')) {
179
+ preferences.sidebarCollapsed = normalizeBoolean(read('codexmateSidebarCollapsed'), false);
180
+ found = true;
181
+ }
182
+ if (has('codexmateStarPrompted')) {
183
+ preferences.starPrompted = normalizeBoolean(read('codexmateStarPrompted'), false);
184
+ found = true;
185
+ }
186
+ if (has('codexmateNavState.v1')) {
187
+ const nav = parseJsonValue(read('codexmateNavState.v1'), null);
188
+ if (isPlainObject(nav)) {
189
+ preferences.navigation = nav;
190
+ found = true;
191
+ }
192
+ }
193
+ const sessionFilters = {};
194
+ const filterMap = {
195
+ source: 'codexmateSessionFilterSource',
196
+ pathFilter: 'codexmateSessionPathFilter',
197
+ query: 'codexmateSessionQuery',
198
+ roleFilter: 'codexmateSessionRoleFilter',
199
+ timePreset: 'codexmateSessionTimePreset',
200
+ sortMode: 'codexmateSessionSortMode'
201
+ };
202
+ for (const [targetKey, storageKey] of Object.entries(filterMap)) {
203
+ const value = read(storageKey);
204
+ if (typeof value === 'string') {
205
+ sessionFilters[targetKey] = value;
206
+ found = true;
207
+ }
208
+ }
209
+ if (Object.keys(sessionFilters).length > 0) preferences.sessionFilters = sessionFilters;
210
+
211
+ const pinnedMap = parseJsonValue(read('codexmateSessionPinnedMap'), null);
212
+ if (isPlainObject(pinnedMap)) {
213
+ preferences.sessionPinnedMap = pinnedMap;
214
+ found = true;
215
+ }
216
+ const claudeConfigs = parseJsonValue(read('claudeConfigs'), null);
217
+ if (isPlainObject(claudeConfigs)) {
218
+ preferences.claudeConfigs = claudeConfigs;
219
+ found = true;
220
+ }
221
+ const openclawConfigs = parseJsonValue(read('openclawConfigs'), null);
222
+ if (isPlainObject(openclawConfigs)) {
223
+ preferences.openclawConfigs = openclawConfigs;
224
+ found = true;
225
+ }
226
+ const toolConfigPermissions = parseJsonValue(read('toolConfigPermissions'), null);
227
+ if (isPlainObject(toolConfigPermissions)) {
228
+ preferences.toolConfigPermissions = toolConfigPermissions;
229
+ found = true;
230
+ }
231
+ const deletedClaudeSettingsImports = parseJsonValue(read('deletedClaudeSettingsImports'), null);
232
+ if (Array.isArray(deletedClaudeSettingsImports)) {
233
+ preferences.deletedClaudeSettingsImports = deletedClaudeSettingsImports;
234
+ found = true;
235
+ }
236
+
237
+ return { preferences, found };
238
+ }
239
+
240
+ function clearLegacyLocalStoragePreferences(storage) {
241
+ if (!storage || typeof storage.removeItem !== 'function') return;
242
+ for (const key of LEGACY_WEB_UI_PREFERENCE_KEYS) {
243
+ try { storage.removeItem(key); } catch (_) {}
244
+ }
245
+ }
246
+
247
+ function mergePreferenceOverrides(current = {}, incoming = {}) {
248
+ const base = isPlainObject(current) ? current : {};
249
+ const next = isPlainObject(incoming) ? incoming : {};
250
+ return {
251
+ ...base,
252
+ ...next,
253
+ ...(isPlainObject(base.navigation) || isPlainObject(next.navigation) ? {
254
+ navigation: {
255
+ ...(isPlainObject(base.navigation) ? base.navigation : {}),
256
+ ...(isPlainObject(next.navigation) ? next.navigation : {})
257
+ }
258
+ } : {}),
259
+ ...(isPlainObject(base.sessionFilters) || isPlainObject(next.sessionFilters) ? {
260
+ sessionFilters: {
261
+ ...(isPlainObject(base.sessionFilters) ? base.sessionFilters : {}),
262
+ ...(isPlainObject(next.sessionFilters) ? next.sessionFilters : {})
263
+ }
264
+ } : {}),
265
+ ...(isPlainObject(base.toolConfigPermissions) || isPlainObject(next.toolConfigPermissions) ? {
266
+ toolConfigPermissions: {
267
+ ...(isPlainObject(base.toolConfigPermissions) ? base.toolConfigPermissions : {}),
268
+ ...(isPlainObject(next.toolConfigPermissions) ? next.toolConfigPermissions : {})
269
+ }
270
+ } : {})
33
271
  };
272
+ }
34
273
 
35
- const normalizeNavigationSnapshot = (vm, source = {}) => {
36
- const currentSkillsTargetApp = vm.skillsTargetApp === 'claude' ? 'claude' : 'codex';
37
- const currentPromptTemplatesMode = vm.promptTemplatesMode === 'manage' ? 'manage' : 'compose';
38
- return {
39
- mainTab: typeof source.mainTab === 'string' ? source.mainTab : vm.mainTab,
40
- configMode: typeof source.configMode === 'string' ? source.configMode : vm.configMode,
41
- settingsTab: typeof source.settingsTab === 'string' ? source.settingsTab : vm.settingsTab,
42
- skillsTargetApp: source.skillsTargetApp === 'claude' || source.skillsTargetApp === 'codex'
43
- ? source.skillsTargetApp
44
- : currentSkillsTargetApp,
45
- promptTemplatesMode: source.promptTemplatesMode === 'manage' || source.promptTemplatesMode === 'compose'
46
- ? source.promptTemplatesMode
47
- : currentPromptTemplatesMode
48
- };
274
+ export function createWebUiPreferencesMethods(options = {}) {
275
+ const api = typeof options.api === 'function' ? options.api : async () => ({});
276
+ const storageOverride = options.storage && typeof options.storage === 'object' ? options.storage : null;
277
+
278
+ const getLocalStorage = () => {
279
+ const storage = storageOverride || (typeof globalThis !== 'undefined' ? globalThis.localStorage : null);
280
+ return storage && typeof storage.getItem === 'function' && typeof storage.removeItem === 'function'
281
+ ? storage
282
+ : null;
49
283
  };
50
284
 
51
285
  return {
286
+ collectLegacyWebUiPreferencesForMigration() {
287
+ return collectLegacyLocalStoragePreferences(getLocalStorage());
288
+ },
289
+
290
+ clearLegacyWebUiPreferencesAfterMigration() {
291
+ clearLegacyLocalStoragePreferences(getLocalStorage());
292
+ },
293
+
52
294
  buildWebUiPreferencesSnapshot(overrides = {}) {
53
- const navigationOverride = overrides && typeof overrides.navigation === 'object' && overrides.navigation
54
- ? overrides.navigation
55
- : null;
295
+ const source = overrides && typeof overrides === 'object' ? overrides : {};
296
+ const navigationOverride = isPlainObject(source.navigation) ? source.navigation : null;
297
+ const sessionFiltersOverride = isPlainObject(source.sessionFilters) ? source.sessionFilters : null;
56
298
  return {
57
299
  shareCommandPrefix: typeof this.normalizeShareCommandPrefix === 'function'
58
- ? this.normalizeShareCommandPrefix(overrides.shareCommandPrefix || this.shareCommandPrefix)
300
+ ? this.normalizeShareCommandPrefix(hasOwn(source, 'shareCommandPrefix') ? source.shareCommandPrefix : this.shareCommandPrefix)
59
301
  : (this.shareCommandPrefix || 'npm start'),
60
302
  sessionTrashEnabled: typeof this.normalizeSessionTrashEnabled === 'function'
61
- ? this.normalizeSessionTrashEnabled(Object.prototype.hasOwnProperty.call(overrides, 'sessionTrashEnabled') ? overrides.sessionTrashEnabled : this.sessionTrashEnabled)
303
+ ? this.normalizeSessionTrashEnabled(hasOwn(source, 'sessionTrashEnabled') ? source.sessionTrashEnabled : this.sessionTrashEnabled)
62
304
  : this.sessionTrashEnabled !== false,
63
305
  sessionTrashRetentionDays: typeof this.normalizeSessionTrashRetentionDays === 'function'
64
- ? this.normalizeSessionTrashRetentionDays(Object.prototype.hasOwnProperty.call(overrides, 'sessionTrashRetentionDays') ? overrides.sessionTrashRetentionDays : this.sessionTrashRetentionDays)
306
+ ? this.normalizeSessionTrashRetentionDays(hasOwn(source, 'sessionTrashRetentionDays') ? source.sessionTrashRetentionDays : this.sessionTrashRetentionDays)
65
307
  : 30,
66
308
  sessionTimelineStyle: typeof this.normalizeSessionTimelineStyle === 'function'
67
- ? this.normalizeSessionTimelineStyle(overrides.sessionTimelineStyle || this.sessionTimelineStyle)
309
+ ? this.normalizeSessionTimelineStyle(hasOwn(source, 'sessionTimelineStyle') ? source.sessionTimelineStyle : this.sessionTimelineStyle)
68
310
  : (this.sessionTimelineStyle === 'bar' ? 'bar' : 'dots'),
69
311
  configTemplateDiffConfirmEnabled: typeof this.normalizeConfigTemplateDiffConfirmEnabled === 'function'
70
- ? this.normalizeConfigTemplateDiffConfirmEnabled(Object.prototype.hasOwnProperty.call(overrides, 'configTemplateDiffConfirmEnabled') ? overrides.configTemplateDiffConfirmEnabled : this.configTemplateDiffConfirmEnabled)
312
+ ? this.normalizeConfigTemplateDiffConfirmEnabled(hasOwn(source, 'configTemplateDiffConfirmEnabled') ? source.configTemplateDiffConfirmEnabled : this.configTemplateDiffConfirmEnabled)
71
313
  : this.configTemplateDiffConfirmEnabled !== false,
72
- sessionsUsageTimeRange: normalizeUsageTimeRange(overrides.sessionsUsageTimeRange || this.sessionsUsageTimeRange),
73
- promptsSubTab: normalizePromptsSubTab(overrides.promptsSubTab || this.promptsSubTab),
74
- projectClaudeMdPath: typeof overrides.projectClaudeMdPath === 'string'
75
- ? overrides.projectClaudeMdPath
314
+ sessionsUsageTimeRange: normalizeUsageTimeRange(hasOwn(source, 'sessionsUsageTimeRange') ? source.sessionsUsageTimeRange : this.sessionsUsageTimeRange),
315
+ promptsSubTab: normalizePromptsSubTab(hasOwn(source, 'promptsSubTab') ? source.promptsSubTab : this.promptsSubTab),
316
+ projectClaudeMdPath: typeof source.projectClaudeMdPath === 'string'
317
+ ? source.projectClaudeMdPath
76
318
  : (typeof this.projectClaudeMdPath === 'string' ? this.projectClaudeMdPath : ''),
319
+ sidebarCollapsed: normalizeBoolean(hasOwn(source, 'sidebarCollapsed') ? source.sidebarCollapsed : this.sidebarCollapsed, false),
320
+ starPrompted: normalizeBoolean(hasOwn(source, 'starPrompted') ? source.starPrompted : this.starPrompted, false),
321
+ taskOrchestrationTabEnabled: normalizeBoolean(hasOwn(source, 'taskOrchestrationTabEnabled') ? source.taskOrchestrationTabEnabled : this.taskOrchestrationTabEnabled, true),
322
+ sessionLoadNativeDialog: normalizeBoolean(hasOwn(source, 'sessionLoadNativeDialog') ? source.sessionLoadNativeDialog : this.sessionLoadNativeDialog, false),
323
+ language: typeof source.language === 'string'
324
+ ? source.language
325
+ : (typeof this.lang === 'string' ? this.lang : ''),
326
+ sessionFilters: normalizeSessionFiltersSnapshot(this, sessionFiltersOverride || {}),
327
+ sessionPinnedMap: normalizePinnedMap(this, hasOwn(source, 'sessionPinnedMap') ? source.sessionPinnedMap : this.sessionPinnedMap),
328
+ claudeConfigs: isPlainObject(source.claudeConfigs) ? source.claudeConfigs : (isPlainObject(this.claudeConfigs) ? this.claudeConfigs : {}),
329
+ currentClaudeConfig: typeof source.currentClaudeConfig === 'string'
330
+ ? source.currentClaudeConfig
331
+ : (typeof this.currentClaudeConfig === 'string' ? this.currentClaudeConfig : ''),
332
+ openclawConfigs: isPlainObject(source.openclawConfigs) ? source.openclawConfigs : (isPlainObject(this.openclawConfigs) ? this.openclawConfigs : {}),
333
+ toolConfigPermissions: isPlainObject(source.toolConfigPermissions) ? source.toolConfigPermissions : (isPlainObject(this.toolConfigPermissions) ? this.toolConfigPermissions : {}),
334
+ deletedClaudeSettingsImports: Array.isArray(source.deletedClaudeSettingsImports)
335
+ ? source.deletedClaudeSettingsImports
336
+ : (Array.isArray(this.deletedClaudeSettingsImports) ? this.deletedClaudeSettingsImports : []),
77
337
  navigation: normalizeNavigationSnapshot(this, navigationOverride || {})
78
338
  };
79
339
  },
@@ -81,39 +341,81 @@ export function createWebUiPreferencesMethods(options = {}) {
81
341
  applyWebUiPreferences(preferences = {}, options = {}) {
82
342
  const source = preferences && typeof preferences === 'object' ? preferences : {};
83
343
  const shouldApplyNavigation = !(options && options.applyNavigation === false);
344
+ let shouldPersistNormalizedClaudeConfigs = false;
84
345
  this.__webUiPreferencesApplying = true;
85
346
  try {
86
347
  if (typeof source.shareCommandPrefix === 'string' && typeof this.normalizeShareCommandPrefix === 'function') {
87
348
  this.shareCommandPrefix = this.normalizeShareCommandPrefix(source.shareCommandPrefix);
88
- setLocalStorageValue('codexmateShareCommandPrefix', this.shareCommandPrefix);
89
349
  }
90
- if (Object.prototype.hasOwnProperty.call(source, 'sessionTrashEnabled') && typeof this.normalizeSessionTrashEnabled === 'function') {
350
+ if (hasOwn(source, 'sessionTrashEnabled') && typeof this.normalizeSessionTrashEnabled === 'function') {
91
351
  this.sessionTrashEnabled = this.normalizeSessionTrashEnabled(source.sessionTrashEnabled);
92
- setLocalStorageValue('codexmateSessionTrashEnabled', this.sessionTrashEnabled ? 'true' : 'false');
93
352
  }
94
- if (Object.prototype.hasOwnProperty.call(source, 'sessionTrashRetentionDays') && typeof this.normalizeSessionTrashRetentionDays === 'function') {
353
+ if (hasOwn(source, 'sessionTrashRetentionDays') && typeof this.normalizeSessionTrashRetentionDays === 'function') {
95
354
  this.sessionTrashRetentionDays = this.normalizeSessionTrashRetentionDays(source.sessionTrashRetentionDays);
96
- setLocalStorageValue('codexmateSessionTrashRetentionDays', this.sessionTrashRetentionDays);
97
355
  }
98
356
  if (typeof source.sessionTimelineStyle === 'string' && typeof this.normalizeSessionTimelineStyle === 'function') {
99
357
  this.sessionTimelineStyle = this.normalizeSessionTimelineStyle(source.sessionTimelineStyle);
100
- setLocalStorageValue('codexmateSessionTimelineStyle', this.sessionTimelineStyle);
101
358
  }
102
- if (Object.prototype.hasOwnProperty.call(source, 'configTemplateDiffConfirmEnabled') && typeof this.normalizeConfigTemplateDiffConfirmEnabled === 'function') {
359
+ if (hasOwn(source, 'configTemplateDiffConfirmEnabled') && typeof this.normalizeConfigTemplateDiffConfirmEnabled === 'function') {
103
360
  this.configTemplateDiffConfirmEnabled = this.normalizeConfigTemplateDiffConfirmEnabled(source.configTemplateDiffConfirmEnabled);
104
- setLocalStorageValue('codexmateConfigTemplateDiffConfirmEnabled', this.configTemplateDiffConfirmEnabled ? 'true' : 'false');
105
361
  }
106
362
  if (typeof source.sessionsUsageTimeRange === 'string') {
107
363
  this.sessionsUsageTimeRange = normalizeUsageTimeRange(source.sessionsUsageTimeRange);
108
- setLocalStorageValue('sessionsUsageTimeRange', this.sessionsUsageTimeRange);
109
364
  }
110
365
  if (typeof source.promptsSubTab === 'string') {
111
366
  this.promptsSubTab = normalizePromptsSubTab(source.promptsSubTab);
112
- setLocalStorageValue('codexmate_prompts_sub_tab', this.promptsSubTab);
113
367
  }
114
368
  if (typeof source.projectClaudeMdPath === 'string') {
115
369
  this.projectClaudeMdPath = source.projectClaudeMdPath;
116
- setLocalStorageValue('codexmate_project_claude_md_path', this.projectClaudeMdPath);
370
+ }
371
+ if (hasOwn(source, 'sidebarCollapsed')) {
372
+ this.sidebarCollapsed = normalizeBoolean(source.sidebarCollapsed, false);
373
+ }
374
+ if (hasOwn(source, 'starPrompted')) {
375
+ this.starPrompted = normalizeBoolean(source.starPrompted, false);
376
+ }
377
+ if (hasOwn(source, 'taskOrchestrationTabEnabled')) {
378
+ this.taskOrchestrationTabEnabled = normalizeBoolean(source.taskOrchestrationTabEnabled, true);
379
+ }
380
+ if (hasOwn(source, 'sessionLoadNativeDialog')) {
381
+ this.sessionLoadNativeDialog = normalizeBoolean(source.sessionLoadNativeDialog, false);
382
+ }
383
+ if (typeof source.language === 'string' && source.language && typeof this.setLang === 'function') {
384
+ this.setLang(source.language, { persist: false });
385
+ }
386
+ if (isPlainObject(source.sessionFilters)) {
387
+ const filters = normalizeSessionFiltersSnapshot(this, source.sessionFilters);
388
+ this.sessionFilterSource = filters.source;
389
+ this.sessionPathFilter = filters.pathFilter;
390
+ this.sessionQuery = filters.query;
391
+ this.sessionRoleFilter = filters.roleFilter;
392
+ this.sessionTimePreset = filters.timePreset;
393
+ this.sessionSortMode = filters.sortMode;
394
+ }
395
+ if (isPlainObject(source.sessionPinnedMap)) {
396
+ this.sessionPinnedMap = normalizePinnedMap(this, source.sessionPinnedMap);
397
+ }
398
+ if (isPlainObject(source.claudeConfigs)) {
399
+ this.claudeConfigs = source.claudeConfigs;
400
+ }
401
+ if (typeof source.currentClaudeConfig === 'string') {
402
+ this.currentClaudeConfig = source.currentClaudeConfig;
403
+ }
404
+ if (isPlainObject(source.claudeConfigs) && typeof this.normalizeStoredClaudeConfigs === 'function') {
405
+ shouldPersistNormalizedClaudeConfigs = this.normalizeStoredClaudeConfigs() === true;
406
+ }
407
+ if (isPlainObject(source.openclawConfigs)) {
408
+ this.openclawConfigs = source.openclawConfigs;
409
+ }
410
+ if (isPlainObject(source.toolConfigPermissions)) {
411
+ this.toolConfigPermissions = {
412
+ codex: source.toolConfigPermissions.codex === true,
413
+ claude: source.toolConfigPermissions.claude === true,
414
+ opencode: source.toolConfigPermissions.opencode === true
415
+ };
416
+ }
417
+ if (Array.isArray(source.deletedClaudeSettingsImports)) {
418
+ this.deletedClaudeSettingsImports = source.deletedClaudeSettingsImports;
117
419
  }
118
420
  if (shouldApplyNavigation && source.navigation && typeof source.navigation === 'object') {
119
421
  const nav = source.navigation;
@@ -135,26 +437,71 @@ export function createWebUiPreferencesMethods(options = {}) {
135
437
  } finally {
136
438
  this.__webUiPreferencesApplying = false;
137
439
  }
440
+ if (shouldPersistNormalizedClaudeConfigs && typeof this.persistWebUiPreferences === 'function') {
441
+ this.persistWebUiPreferences({
442
+ claudeConfigs: this.claudeConfigs,
443
+ currentClaudeConfig: this.currentClaudeConfig || ''
444
+ });
445
+ }
138
446
  },
139
447
 
140
448
  async loadWebUiPreferences(options = {}) {
449
+ const loadOptions = options && typeof options === 'object' ? options : {};
450
+ const legacy = this.collectLegacyWebUiPreferencesForMigration();
141
451
  try {
142
452
  const res = await api('get-web-ui-preferences');
143
- if (res && res.preferences && typeof res.preferences === 'object') {
144
- this.applyWebUiPreferences(res.preferences, options && typeof options === 'object' ? options : {});
453
+ const current = res && res.preferences && typeof res.preferences === 'object' ? res.preferences : {};
454
+ const merged = legacy && legacy.found
455
+ ? this.buildWebUiPreferencesSnapshot({
456
+ ...current,
457
+ ...legacy.preferences,
458
+ navigation: {
459
+ ...(isPlainObject(current.navigation) ? current.navigation : {}),
460
+ ...(isPlainObject(legacy.preferences.navigation) ? legacy.preferences.navigation : {})
461
+ },
462
+ sessionFilters: {
463
+ ...(isPlainObject(current.sessionFilters) ? current.sessionFilters : {}),
464
+ ...(isPlainObject(legacy.preferences.sessionFilters) ? legacy.preferences.sessionFilters : {})
465
+ }
466
+ })
467
+ : current;
468
+ if (merged && typeof merged === 'object') {
469
+ this.applyWebUiPreferences(merged, loadOptions);
145
470
  }
146
- } catch (_) {}
471
+ if (legacy && legacy.found) {
472
+ await api('set-web-ui-preferences', { preferences: this.buildWebUiPreferencesSnapshot(merged) });
473
+ this.clearLegacyWebUiPreferencesAfterMigration();
474
+ }
475
+ } catch (_) {
476
+ if (legacy && legacy.found) {
477
+ this.applyWebUiPreferences(legacy.preferences, loadOptions);
478
+ }
479
+ }
480
+ },
481
+
482
+ flushWebUiPreferences() {
483
+ if (this.__webUiPreferencesPersistTimer) {
484
+ clearTimeout(this.__webUiPreferencesPersistTimer);
485
+ this.__webUiPreferencesPersistTimer = 0;
486
+ }
487
+ const pending = this.__webUiPreferencesPendingOverrides;
488
+ if (!pending || typeof pending !== 'object') return;
489
+ this.__webUiPreferencesPendingOverrides = null;
490
+ const snapshot = this.buildWebUiPreferencesSnapshot(pending);
491
+ api('set-web-ui-preferences', { preferences: snapshot }).catch(() => {});
147
492
  },
148
493
 
149
494
  persistWebUiPreferences(overrides = {}) {
150
495
  if (this.__webUiPreferencesApplying) return;
151
- const snapshot = this.buildWebUiPreferencesSnapshot(overrides && typeof overrides === 'object' ? overrides : {});
496
+ this.__webUiPreferencesPendingOverrides = mergePreferenceOverrides(
497
+ this.__webUiPreferencesPendingOverrides,
498
+ overrides && typeof overrides === 'object' ? overrides : {}
499
+ );
152
500
  if (this.__webUiPreferencesPersistTimer) {
153
501
  clearTimeout(this.__webUiPreferencesPersistTimer);
154
502
  }
155
503
  this.__webUiPreferencesPersistTimer = setTimeout(() => {
156
- this.__webUiPreferencesPersistTimer = 0;
157
- api('set-web-ui-preferences', { preferences: snapshot }).catch(() => {});
504
+ this.flushWebUiPreferences();
158
505
  }, 120);
159
506
  }
160
507
  };
@@ -10,7 +10,7 @@ export function normalizeConfigTemplateDiffConfirmEnabled(value) {
10
10
  }
11
11
 
12
12
  export function loadConfigTemplateDiffConfirmEnabledFromStorage(storage = null) {
13
- const target = storage || (typeof localStorage !== 'undefined' ? localStorage : null);
13
+ const target = storage || null;
14
14
  if (!target || typeof target.getItem !== 'function') {
15
15
  return true;
16
16
  }
@@ -22,7 +22,7 @@ export function loadConfigTemplateDiffConfirmEnabledFromStorage(storage = null)
22
22
  }
23
23
 
24
24
  export function persistConfigTemplateDiffConfirmEnabledToStorage(enabled, storage = null) {
25
- const target = storage || (typeof localStorage !== 'undefined' ? localStorage : null);
25
+ const target = storage || null;
26
26
  if (!target || typeof target.setItem !== 'function') {
27
27
  return;
28
28
  }
@@ -30,4 +30,3 @@ export function persistConfigTemplateDiffConfirmEnabledToStorage(enabled, storag
30
30
  target.setItem(CONFIG_TEMPLATE_DIFF_CONFIRM_STORAGE_KEY, enabled ? 'true' : 'false');
31
31
  } catch (_) {}
32
32
  }
33
-