codexmate 0.0.56 → 0.1.1

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 (42) hide show
  1. package/README.md +2 -2
  2. package/README.vi.md +1 -0
  3. package/README.zh.md +2 -2
  4. package/cli/openai-bridge-retry.js +62 -0
  5. package/cli/openai-bridge-runtime.js +1819 -0
  6. package/cli/openai-bridge.js +137 -2048
  7. package/cli.js +749 -133
  8. package/lib/task-orchestrator.js +90 -21
  9. package/lib/task-workspace-chat.js +292 -0
  10. package/package.json +2 -2
  11. package/web-ui/app.js +67 -131
  12. package/web-ui/modules/app.computed.main-tabs.mjs +304 -7
  13. package/web-ui/modules/app.methods.agents.mjs +192 -1
  14. package/web-ui/modules/app.methods.claude-config.mjs +65 -25
  15. package/web-ui/modules/app.methods.navigation.mjs +67 -83
  16. package/web-ui/modules/app.methods.openclaw-editing.mjs +3 -6
  17. package/web-ui/modules/app.methods.providers.mjs +40 -10
  18. package/web-ui/modules/app.methods.session-actions.mjs +1 -12
  19. package/web-ui/modules/app.methods.session-browser.mjs +23 -54
  20. package/web-ui/modules/app.methods.session-trash.mjs +0 -1
  21. package/web-ui/modules/app.methods.startup-claude.mjs +16 -31
  22. package/web-ui/modules/app.methods.task-orchestration.mjs +347 -8
  23. package/web-ui/modules/app.methods.tool-config-permissions.mjs +0 -3
  24. package/web-ui/modules/app.methods.web-ui-preferences.mjs +442 -68
  25. package/web-ui/modules/config-template-confirm-pref.mjs +2 -3
  26. package/web-ui/modules/i18n/locales/en.mjs +181 -30
  27. package/web-ui/modules/i18n/locales/ja.mjs +178 -27
  28. package/web-ui/modules/i18n/locales/vi.mjs +180 -29
  29. package/web-ui/modules/i18n/locales/zh-tw.mjs +181 -30
  30. package/web-ui/modules/i18n/locales/zh.mjs +183 -32
  31. package/web-ui/modules/i18n.mjs +5 -12
  32. package/web-ui/modules/sessions-filters-url.mjs +1 -2
  33. package/web-ui/partials/index/layout-header.html +44 -38
  34. package/web-ui/partials/index/modals-basic.html +26 -0
  35. package/web-ui/partials/index/panel-orchestration.html +489 -282
  36. package/web-ui/partials/index/panel-prompts.html +66 -3
  37. package/web-ui/res/web-ui-render.precompiled.js +1181 -604
  38. package/web-ui/styles/layout-shell.css +157 -1
  39. package/web-ui/styles/modals-core.css +173 -0
  40. package/web-ui/styles/navigation-panels.css +11 -0
  41. package/web-ui/styles/responsive.css +32 -0
  42. package/web-ui/styles/task-orchestration.css +2161 -4
@@ -1,79 +1,360 @@
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
+ if (normalized === 'claude-project') return normalized;
66
+ return 'codex';
67
+ }
68
+
69
+ function normalizePromptPresets(value) {
70
+ if (!Array.isArray(value)) return [];
71
+ const seen = new Set();
72
+ return value
73
+ .filter(item => item && typeof item === 'object')
74
+ .map((item, index) => {
75
+ const id = typeof item.id === 'string' && item.id.trim()
76
+ ? item.id.trim()
77
+ : `preset-${Date.now()}-${index}`;
78
+ const name = typeof item.name === 'string' ? item.name.trim() : '';
79
+ const content = typeof item.content === 'string' ? item.content : '';
80
+ const updatedAt = typeof item.updatedAt === 'string' && item.updatedAt.trim()
81
+ ? item.updatedAt.trim()
82
+ : new Date(0).toISOString();
83
+ return { id, name, content, updatedAt };
84
+ })
85
+ .filter(item => item.id && item.name && !seen.has(item.id) && (seen.add(item.id), true));
86
+ }
87
+
88
+ function normalizeSessionSortMode(value) {
89
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
90
+ return normalized === 'hot' ? 'hot' : 'time';
91
+ }
92
+
93
+ function normalizeNavigationSnapshot(vm, source = {}) {
94
+ const currentSkillsTargetApp = vm.skillsTargetApp === 'claude' ? 'claude' : 'codex';
95
+ const currentPromptTemplatesMode = vm.promptTemplatesMode === 'manage' ? 'manage' : 'compose';
96
+ return {
97
+ mainTab: typeof source.mainTab === 'string' ? source.mainTab : vm.mainTab,
98
+ configMode: typeof source.configMode === 'string' ? source.configMode : vm.configMode,
99
+ settingsTab: typeof source.settingsTab === 'string' ? source.settingsTab : vm.settingsTab,
100
+ skillsTargetApp: source.skillsTargetApp === 'claude' || source.skillsTargetApp === 'codex'
101
+ ? source.skillsTargetApp
102
+ : currentSkillsTargetApp,
103
+ promptTemplatesMode: source.promptTemplatesMode === 'manage' || source.promptTemplatesMode === 'compose'
104
+ ? source.promptTemplatesMode
105
+ : currentPromptTemplatesMode
10
106
  };
107
+ }
11
108
 
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 (_) {}
109
+ function normalizeSessionFiltersSnapshot(vm, source = {}) {
110
+ const incoming = isPlainObject(source) ? source : {};
111
+ const normalizeRole = typeof vm.normalizeSessionRoleFilter === 'function'
112
+ ? vm.normalizeSessionRoleFilter.bind(vm)
113
+ : ((value) => {
114
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
115
+ return ['user', 'assistant', 'system', 'tool'].includes(normalized) ? normalized : 'all';
116
+ });
117
+ const normalizeTime = typeof vm.normalizeSessionTimePreset === 'function'
118
+ ? vm.normalizeSessionTimePreset.bind(vm)
119
+ : ((value) => {
120
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
121
+ return ['24h', '7d', '30d'].includes(normalized) ? normalized : 'all';
122
+ });
123
+ return {
124
+ source: typeof incoming.source === 'string' ? incoming.source : vm.sessionFilterSource,
125
+ pathFilter: typeof incoming.pathFilter === 'string' ? incoming.pathFilter : (vm.sessionPathFilter || ''),
126
+ query: typeof incoming.query === 'string' ? incoming.query : (vm.sessionQuery || ''),
127
+ roleFilter: normalizeRole(hasOwn(incoming, 'roleFilter') ? incoming.roleFilter : vm.sessionRoleFilter),
128
+ timePreset: normalizeTime(hasOwn(incoming, 'timePreset') ? incoming.timePreset : vm.sessionTimePreset),
129
+ sortMode: typeof vm.normalizeSessionSortMode === 'function'
130
+ ? vm.normalizeSessionSortMode(hasOwn(incoming, 'sortMode') ? incoming.sortMode : vm.sessionSortMode)
131
+ : normalizeSessionSortMode(hasOwn(incoming, 'sortMode') ? incoming.sortMode : vm.sessionSortMode)
132
+ };
133
+ }
134
+
135
+ function normalizePinnedMap(vm, value) {
136
+ if (typeof vm.normalizeSessionPinnedMap === 'function') {
137
+ return vm.normalizeSessionPinnedMap(value);
138
+ }
139
+ const source = isPlainObject(value) ? value : {};
140
+ const next = {};
141
+ for (const [key, item] of Object.entries(source)) {
142
+ if (!key) continue;
143
+ const numeric = Number(item);
144
+ if (!Number.isFinite(numeric) || numeric <= 0) continue;
145
+ next[key] = Math.floor(numeric);
146
+ }
147
+ return next;
148
+ }
149
+
150
+ function readStorageValue(storage, key) {
151
+ if (!storage || typeof storage.getItem !== 'function') return null;
152
+ try {
153
+ return storage.getItem(key);
154
+ } catch (_) {
155
+ return null;
156
+ }
157
+ }
158
+
159
+ function collectLegacyLocalStoragePreferences(storage) {
160
+ const read = (key) => readStorageValue(storage, key);
161
+ const has = (key) => read(key) !== null;
162
+ const preferences = {};
163
+ let found = false;
164
+
165
+ const assignString = (targetKey, storageKey) => {
166
+ const value = read(storageKey);
167
+ if (typeof value === 'string') {
168
+ preferences[targetKey] = value;
169
+ found = true;
170
+ }
22
171
  };
23
172
 
24
- const normalizeUsageTimeRange = (value) => {
25
- const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
26
- if (normalized === 'all' || normalized === '30d') return normalized;
27
- return '7d';
173
+ assignString('shareCommandPrefix', 'codexmateShareCommandPrefix');
174
+ assignString('sessionTrashRetentionDays', 'codexmateSessionTrashRetentionDays');
175
+ assignString('sessionTimelineStyle', 'codexmateSessionTimelineStyle');
176
+ assignString('sessionsUsageTimeRange', 'sessionsUsageTimeRange');
177
+ assignString('promptsSubTab', 'codexmate_prompts_sub_tab');
178
+ assignString('projectClaudeMdPath', 'codexmate_project_claude_md_path');
179
+ assignString('language', 'codexmateLang');
180
+ assignString('currentClaudeConfig', 'currentClaudeConfig');
181
+
182
+ if (has('codexmateSessionTrashEnabled')) {
183
+ preferences.sessionTrashEnabled = normalizeBoolean(read('codexmateSessionTrashEnabled'), true);
184
+ found = true;
185
+ }
186
+ if (has('codexmateConfigTemplateDiffConfirmEnabled')) {
187
+ preferences.configTemplateDiffConfirmEnabled = normalizeBoolean(read('codexmateConfigTemplateDiffConfirmEnabled'), true);
188
+ found = true;
189
+ }
190
+ if (has('codexmateTaskOrchestrationTabEnabled')) {
191
+ preferences.taskOrchestrationTabEnabled = normalizeBoolean(read('codexmateTaskOrchestrationTabEnabled'), true);
192
+ found = true;
193
+ }
194
+ if (has('codexmateSessionLoadNativeDialog')) {
195
+ preferences.sessionLoadNativeDialog = normalizeBoolean(read('codexmateSessionLoadNativeDialog'), false);
196
+ found = true;
197
+ }
198
+ if (has('codexmateSidebarCollapsed')) {
199
+ preferences.sidebarCollapsed = normalizeBoolean(read('codexmateSidebarCollapsed'), false);
200
+ found = true;
201
+ }
202
+ if (has('codexmateStarPrompted')) {
203
+ preferences.starPrompted = normalizeBoolean(read('codexmateStarPrompted'), false);
204
+ found = true;
205
+ }
206
+ if (has('codexmateNavState.v1')) {
207
+ const nav = parseJsonValue(read('codexmateNavState.v1'), null);
208
+ if (isPlainObject(nav)) {
209
+ preferences.navigation = nav;
210
+ found = true;
211
+ }
212
+ }
213
+ const sessionFilters = {};
214
+ const filterMap = {
215
+ source: 'codexmateSessionFilterSource',
216
+ pathFilter: 'codexmateSessionPathFilter',
217
+ query: 'codexmateSessionQuery',
218
+ roleFilter: 'codexmateSessionRoleFilter',
219
+ timePreset: 'codexmateSessionTimePreset',
220
+ sortMode: 'codexmateSessionSortMode'
28
221
  };
222
+ for (const [targetKey, storageKey] of Object.entries(filterMap)) {
223
+ const value = read(storageKey);
224
+ if (typeof value === 'string') {
225
+ sessionFilters[targetKey] = value;
226
+ found = true;
227
+ }
228
+ }
229
+ if (Object.keys(sessionFilters).length > 0) preferences.sessionFilters = sessionFilters;
230
+
231
+ const pinnedMap = parseJsonValue(read('codexmateSessionPinnedMap'), null);
232
+ if (isPlainObject(pinnedMap)) {
233
+ preferences.sessionPinnedMap = pinnedMap;
234
+ found = true;
235
+ }
236
+ const claudeConfigs = parseJsonValue(read('claudeConfigs'), null);
237
+ if (isPlainObject(claudeConfigs)) {
238
+ preferences.claudeConfigs = claudeConfigs;
239
+ found = true;
240
+ }
241
+ const openclawConfigs = parseJsonValue(read('openclawConfigs'), null);
242
+ if (isPlainObject(openclawConfigs)) {
243
+ preferences.openclawConfigs = openclawConfigs;
244
+ found = true;
245
+ }
246
+ const toolConfigPermissions = parseJsonValue(read('toolConfigPermissions'), null);
247
+ if (isPlainObject(toolConfigPermissions)) {
248
+ preferences.toolConfigPermissions = toolConfigPermissions;
249
+ found = true;
250
+ }
251
+ const deletedClaudeSettingsImports = parseJsonValue(read('deletedClaudeSettingsImports'), null);
252
+ if (Array.isArray(deletedClaudeSettingsImports)) {
253
+ preferences.deletedClaudeSettingsImports = deletedClaudeSettingsImports;
254
+ found = true;
255
+ }
256
+
257
+ return { preferences, found };
258
+ }
259
+
260
+ function clearLegacyLocalStoragePreferences(storage) {
261
+ if (!storage || typeof storage.removeItem !== 'function') return;
262
+ for (const key of LEGACY_WEB_UI_PREFERENCE_KEYS) {
263
+ try { storage.removeItem(key); } catch (_) {}
264
+ }
265
+ }
29
266
 
30
- const normalizePromptsSubTab = (value) => {
31
- const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
32
- return normalized === 'claude-project' ? 'claude-project' : 'codex';
267
+ function mergePreferenceOverrides(current = {}, incoming = {}) {
268
+ const base = isPlainObject(current) ? current : {};
269
+ const next = isPlainObject(incoming) ? incoming : {};
270
+ return {
271
+ ...base,
272
+ ...next,
273
+ ...(isPlainObject(base.navigation) || isPlainObject(next.navigation) ? {
274
+ navigation: {
275
+ ...(isPlainObject(base.navigation) ? base.navigation : {}),
276
+ ...(isPlainObject(next.navigation) ? next.navigation : {})
277
+ }
278
+ } : {}),
279
+ ...(isPlainObject(base.sessionFilters) || isPlainObject(next.sessionFilters) ? {
280
+ sessionFilters: {
281
+ ...(isPlainObject(base.sessionFilters) ? base.sessionFilters : {}),
282
+ ...(isPlainObject(next.sessionFilters) ? next.sessionFilters : {})
283
+ }
284
+ } : {}),
285
+ ...(isPlainObject(base.toolConfigPermissions) || isPlainObject(next.toolConfigPermissions) ? {
286
+ toolConfigPermissions: {
287
+ ...(isPlainObject(base.toolConfigPermissions) ? base.toolConfigPermissions : {}),
288
+ ...(isPlainObject(next.toolConfigPermissions) ? next.toolConfigPermissions : {})
289
+ }
290
+ } : {})
33
291
  };
292
+ }
34
293
 
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
- };
294
+ export function createWebUiPreferencesMethods(options = {}) {
295
+ const api = typeof options.api === 'function' ? options.api : async () => ({});
296
+ const storageOverride = options.storage && typeof options.storage === 'object' ? options.storage : null;
297
+
298
+ const getLocalStorage = () => {
299
+ const storage = storageOverride || (typeof globalThis !== 'undefined' ? globalThis.localStorage : null);
300
+ return storage && typeof storage.getItem === 'function' && typeof storage.removeItem === 'function'
301
+ ? storage
302
+ : null;
49
303
  };
50
304
 
51
305
  return {
306
+ collectLegacyWebUiPreferencesForMigration() {
307
+ return collectLegacyLocalStoragePreferences(getLocalStorage());
308
+ },
309
+
310
+ clearLegacyWebUiPreferencesAfterMigration() {
311
+ clearLegacyLocalStoragePreferences(getLocalStorage());
312
+ },
313
+
52
314
  buildWebUiPreferencesSnapshot(overrides = {}) {
53
- const navigationOverride = overrides && typeof overrides.navigation === 'object' && overrides.navigation
54
- ? overrides.navigation
55
- : null;
315
+ const source = overrides && typeof overrides === 'object' ? overrides : {};
316
+ const navigationOverride = isPlainObject(source.navigation) ? source.navigation : null;
317
+ const sessionFiltersOverride = isPlainObject(source.sessionFilters) ? source.sessionFilters : null;
56
318
  return {
57
319
  shareCommandPrefix: typeof this.normalizeShareCommandPrefix === 'function'
58
- ? this.normalizeShareCommandPrefix(overrides.shareCommandPrefix || this.shareCommandPrefix)
320
+ ? this.normalizeShareCommandPrefix(hasOwn(source, 'shareCommandPrefix') ? source.shareCommandPrefix : this.shareCommandPrefix)
59
321
  : (this.shareCommandPrefix || 'npm start'),
60
322
  sessionTrashEnabled: typeof this.normalizeSessionTrashEnabled === 'function'
61
- ? this.normalizeSessionTrashEnabled(Object.prototype.hasOwnProperty.call(overrides, 'sessionTrashEnabled') ? overrides.sessionTrashEnabled : this.sessionTrashEnabled)
323
+ ? this.normalizeSessionTrashEnabled(hasOwn(source, 'sessionTrashEnabled') ? source.sessionTrashEnabled : this.sessionTrashEnabled)
62
324
  : this.sessionTrashEnabled !== false,
63
325
  sessionTrashRetentionDays: typeof this.normalizeSessionTrashRetentionDays === 'function'
64
- ? this.normalizeSessionTrashRetentionDays(Object.prototype.hasOwnProperty.call(overrides, 'sessionTrashRetentionDays') ? overrides.sessionTrashRetentionDays : this.sessionTrashRetentionDays)
326
+ ? this.normalizeSessionTrashRetentionDays(hasOwn(source, 'sessionTrashRetentionDays') ? source.sessionTrashRetentionDays : this.sessionTrashRetentionDays)
65
327
  : 30,
66
328
  sessionTimelineStyle: typeof this.normalizeSessionTimelineStyle === 'function'
67
- ? this.normalizeSessionTimelineStyle(overrides.sessionTimelineStyle || this.sessionTimelineStyle)
329
+ ? this.normalizeSessionTimelineStyle(hasOwn(source, 'sessionTimelineStyle') ? source.sessionTimelineStyle : this.sessionTimelineStyle)
68
330
  : (this.sessionTimelineStyle === 'bar' ? 'bar' : 'dots'),
69
331
  configTemplateDiffConfirmEnabled: typeof this.normalizeConfigTemplateDiffConfirmEnabled === 'function'
70
- ? this.normalizeConfigTemplateDiffConfirmEnabled(Object.prototype.hasOwnProperty.call(overrides, 'configTemplateDiffConfirmEnabled') ? overrides.configTemplateDiffConfirmEnabled : this.configTemplateDiffConfirmEnabled)
332
+ ? this.normalizeConfigTemplateDiffConfirmEnabled(hasOwn(source, 'configTemplateDiffConfirmEnabled') ? source.configTemplateDiffConfirmEnabled : this.configTemplateDiffConfirmEnabled)
71
333
  : 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
334
+ sessionsUsageTimeRange: normalizeUsageTimeRange(hasOwn(source, 'sessionsUsageTimeRange') ? source.sessionsUsageTimeRange : this.sessionsUsageTimeRange),
335
+ promptsSubTab: normalizePromptsSubTab(hasOwn(source, 'promptsSubTab') ? source.promptsSubTab : this.promptsSubTab),
336
+ promptPresets: normalizePromptPresets(hasOwn(source, 'promptPresets') ? source.promptPresets : this.promptPresets),
337
+ projectClaudeMdPath: typeof source.projectClaudeMdPath === 'string'
338
+ ? source.projectClaudeMdPath
76
339
  : (typeof this.projectClaudeMdPath === 'string' ? this.projectClaudeMdPath : ''),
340
+ sidebarCollapsed: normalizeBoolean(hasOwn(source, 'sidebarCollapsed') ? source.sidebarCollapsed : this.sidebarCollapsed, false),
341
+ starPrompted: normalizeBoolean(hasOwn(source, 'starPrompted') ? source.starPrompted : this.starPrompted, false),
342
+ taskOrchestrationTabEnabled: normalizeBoolean(hasOwn(source, 'taskOrchestrationTabEnabled') ? source.taskOrchestrationTabEnabled : this.taskOrchestrationTabEnabled, true),
343
+ sessionLoadNativeDialog: normalizeBoolean(hasOwn(source, 'sessionLoadNativeDialog') ? source.sessionLoadNativeDialog : this.sessionLoadNativeDialog, false),
344
+ language: typeof source.language === 'string'
345
+ ? source.language
346
+ : (typeof this.lang === 'string' ? this.lang : ''),
347
+ sessionFilters: normalizeSessionFiltersSnapshot(this, sessionFiltersOverride || {}),
348
+ sessionPinnedMap: normalizePinnedMap(this, hasOwn(source, 'sessionPinnedMap') ? source.sessionPinnedMap : this.sessionPinnedMap),
349
+ claudeConfigs: isPlainObject(source.claudeConfigs) ? source.claudeConfigs : (isPlainObject(this.claudeConfigs) ? this.claudeConfigs : {}),
350
+ currentClaudeConfig: typeof source.currentClaudeConfig === 'string'
351
+ ? source.currentClaudeConfig
352
+ : (typeof this.currentClaudeConfig === 'string' ? this.currentClaudeConfig : ''),
353
+ openclawConfigs: isPlainObject(source.openclawConfigs) ? source.openclawConfigs : (isPlainObject(this.openclawConfigs) ? this.openclawConfigs : {}),
354
+ toolConfigPermissions: isPlainObject(source.toolConfigPermissions) ? source.toolConfigPermissions : (isPlainObject(this.toolConfigPermissions) ? this.toolConfigPermissions : {}),
355
+ deletedClaudeSettingsImports: Array.isArray(source.deletedClaudeSettingsImports)
356
+ ? source.deletedClaudeSettingsImports
357
+ : (Array.isArray(this.deletedClaudeSettingsImports) ? this.deletedClaudeSettingsImports : []),
77
358
  navigation: normalizeNavigationSnapshot(this, navigationOverride || {})
78
359
  };
79
360
  },
@@ -81,39 +362,87 @@ export function createWebUiPreferencesMethods(options = {}) {
81
362
  applyWebUiPreferences(preferences = {}, options = {}) {
82
363
  const source = preferences && typeof preferences === 'object' ? preferences : {};
83
364
  const shouldApplyNavigation = !(options && options.applyNavigation === false);
365
+ let shouldPersistNormalizedClaudeConfigs = false;
84
366
  this.__webUiPreferencesApplying = true;
85
367
  try {
86
368
  if (typeof source.shareCommandPrefix === 'string' && typeof this.normalizeShareCommandPrefix === 'function') {
87
369
  this.shareCommandPrefix = this.normalizeShareCommandPrefix(source.shareCommandPrefix);
88
- setLocalStorageValue('codexmateShareCommandPrefix', this.shareCommandPrefix);
89
370
  }
90
- if (Object.prototype.hasOwnProperty.call(source, 'sessionTrashEnabled') && typeof this.normalizeSessionTrashEnabled === 'function') {
371
+ if (hasOwn(source, 'sessionTrashEnabled') && typeof this.normalizeSessionTrashEnabled === 'function') {
91
372
  this.sessionTrashEnabled = this.normalizeSessionTrashEnabled(source.sessionTrashEnabled);
92
- setLocalStorageValue('codexmateSessionTrashEnabled', this.sessionTrashEnabled ? 'true' : 'false');
93
373
  }
94
- if (Object.prototype.hasOwnProperty.call(source, 'sessionTrashRetentionDays') && typeof this.normalizeSessionTrashRetentionDays === 'function') {
374
+ if (hasOwn(source, 'sessionTrashRetentionDays') && typeof this.normalizeSessionTrashRetentionDays === 'function') {
95
375
  this.sessionTrashRetentionDays = this.normalizeSessionTrashRetentionDays(source.sessionTrashRetentionDays);
96
- setLocalStorageValue('codexmateSessionTrashRetentionDays', this.sessionTrashRetentionDays);
97
376
  }
98
377
  if (typeof source.sessionTimelineStyle === 'string' && typeof this.normalizeSessionTimelineStyle === 'function') {
99
378
  this.sessionTimelineStyle = this.normalizeSessionTimelineStyle(source.sessionTimelineStyle);
100
- setLocalStorageValue('codexmateSessionTimelineStyle', this.sessionTimelineStyle);
101
379
  }
102
- if (Object.prototype.hasOwnProperty.call(source, 'configTemplateDiffConfirmEnabled') && typeof this.normalizeConfigTemplateDiffConfirmEnabled === 'function') {
380
+ if (hasOwn(source, 'configTemplateDiffConfirmEnabled') && typeof this.normalizeConfigTemplateDiffConfirmEnabled === 'function') {
103
381
  this.configTemplateDiffConfirmEnabled = this.normalizeConfigTemplateDiffConfirmEnabled(source.configTemplateDiffConfirmEnabled);
104
- setLocalStorageValue('codexmateConfigTemplateDiffConfirmEnabled', this.configTemplateDiffConfirmEnabled ? 'true' : 'false');
105
382
  }
106
383
  if (typeof source.sessionsUsageTimeRange === 'string') {
107
384
  this.sessionsUsageTimeRange = normalizeUsageTimeRange(source.sessionsUsageTimeRange);
108
- setLocalStorageValue('sessionsUsageTimeRange', this.sessionsUsageTimeRange);
109
385
  }
110
386
  if (typeof source.promptsSubTab === 'string') {
111
387
  this.promptsSubTab = normalizePromptsSubTab(source.promptsSubTab);
112
- setLocalStorageValue('codexmate_prompts_sub_tab', this.promptsSubTab);
388
+ }
389
+ if (Array.isArray(source.promptPresets)) {
390
+ this.promptPresets = normalizePromptPresets(source.promptPresets);
391
+ if (this.selectedPromptPresetId && !this.promptPresets.some(preset => preset.id === this.selectedPromptPresetId)) {
392
+ this.selectedPromptPresetId = '';
393
+ }
113
394
  }
114
395
  if (typeof source.projectClaudeMdPath === 'string') {
115
396
  this.projectClaudeMdPath = source.projectClaudeMdPath;
116
- setLocalStorageValue('codexmate_project_claude_md_path', this.projectClaudeMdPath);
397
+ }
398
+ if (hasOwn(source, 'sidebarCollapsed')) {
399
+ this.sidebarCollapsed = normalizeBoolean(source.sidebarCollapsed, false);
400
+ }
401
+ if (hasOwn(source, 'starPrompted')) {
402
+ this.starPrompted = normalizeBoolean(source.starPrompted, false);
403
+ }
404
+ if (hasOwn(source, 'taskOrchestrationTabEnabled')) {
405
+ this.taskOrchestrationTabEnabled = normalizeBoolean(source.taskOrchestrationTabEnabled, true);
406
+ }
407
+ if (hasOwn(source, 'sessionLoadNativeDialog')) {
408
+ this.sessionLoadNativeDialog = normalizeBoolean(source.sessionLoadNativeDialog, false);
409
+ }
410
+ if (typeof source.language === 'string' && source.language && typeof this.setLang === 'function') {
411
+ this.setLang(source.language, { persist: false });
412
+ }
413
+ if (isPlainObject(source.sessionFilters)) {
414
+ const filters = normalizeSessionFiltersSnapshot(this, source.sessionFilters);
415
+ this.sessionFilterSource = filters.source;
416
+ this.sessionPathFilter = filters.pathFilter;
417
+ this.sessionQuery = filters.query;
418
+ this.sessionRoleFilter = filters.roleFilter;
419
+ this.sessionTimePreset = filters.timePreset;
420
+ this.sessionSortMode = filters.sortMode;
421
+ }
422
+ if (isPlainObject(source.sessionPinnedMap)) {
423
+ this.sessionPinnedMap = normalizePinnedMap(this, source.sessionPinnedMap);
424
+ }
425
+ if (isPlainObject(source.claudeConfigs)) {
426
+ this.claudeConfigs = source.claudeConfigs;
427
+ }
428
+ if (typeof source.currentClaudeConfig === 'string') {
429
+ this.currentClaudeConfig = source.currentClaudeConfig;
430
+ }
431
+ if (isPlainObject(source.claudeConfigs) && typeof this.normalizeStoredClaudeConfigs === 'function') {
432
+ shouldPersistNormalizedClaudeConfigs = this.normalizeStoredClaudeConfigs() === true;
433
+ }
434
+ if (isPlainObject(source.openclawConfigs)) {
435
+ this.openclawConfigs = source.openclawConfigs;
436
+ }
437
+ if (isPlainObject(source.toolConfigPermissions)) {
438
+ this.toolConfigPermissions = {
439
+ codex: source.toolConfigPermissions.codex === true,
440
+ claude: source.toolConfigPermissions.claude === true,
441
+ opencode: source.toolConfigPermissions.opencode === true
442
+ };
443
+ }
444
+ if (Array.isArray(source.deletedClaudeSettingsImports)) {
445
+ this.deletedClaudeSettingsImports = source.deletedClaudeSettingsImports;
117
446
  }
118
447
  if (shouldApplyNavigation && source.navigation && typeof source.navigation === 'object') {
119
448
  const nav = source.navigation;
@@ -135,26 +464,71 @@ export function createWebUiPreferencesMethods(options = {}) {
135
464
  } finally {
136
465
  this.__webUiPreferencesApplying = false;
137
466
  }
467
+ if (shouldPersistNormalizedClaudeConfigs && typeof this.persistWebUiPreferences === 'function') {
468
+ this.persistWebUiPreferences({
469
+ claudeConfigs: this.claudeConfigs,
470
+ currentClaudeConfig: this.currentClaudeConfig || ''
471
+ });
472
+ }
138
473
  },
139
474
 
140
475
  async loadWebUiPreferences(options = {}) {
476
+ const loadOptions = options && typeof options === 'object' ? options : {};
477
+ const legacy = this.collectLegacyWebUiPreferencesForMigration();
141
478
  try {
142
479
  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 : {});
480
+ const current = res && res.preferences && typeof res.preferences === 'object' ? res.preferences : {};
481
+ const merged = legacy && legacy.found
482
+ ? this.buildWebUiPreferencesSnapshot({
483
+ ...current,
484
+ ...legacy.preferences,
485
+ navigation: {
486
+ ...(isPlainObject(current.navigation) ? current.navigation : {}),
487
+ ...(isPlainObject(legacy.preferences.navigation) ? legacy.preferences.navigation : {})
488
+ },
489
+ sessionFilters: {
490
+ ...(isPlainObject(current.sessionFilters) ? current.sessionFilters : {}),
491
+ ...(isPlainObject(legacy.preferences.sessionFilters) ? legacy.preferences.sessionFilters : {})
492
+ }
493
+ })
494
+ : current;
495
+ if (merged && typeof merged === 'object') {
496
+ this.applyWebUiPreferences(merged, loadOptions);
497
+ }
498
+ if (legacy && legacy.found) {
499
+ await api('set-web-ui-preferences', { preferences: this.buildWebUiPreferencesSnapshot(merged) });
500
+ this.clearLegacyWebUiPreferencesAfterMigration();
501
+ }
502
+ } catch (_) {
503
+ if (legacy && legacy.found) {
504
+ this.applyWebUiPreferences(legacy.preferences, loadOptions);
145
505
  }
146
- } catch (_) {}
506
+ }
507
+ },
508
+
509
+ flushWebUiPreferences() {
510
+ if (this.__webUiPreferencesPersistTimer) {
511
+ clearTimeout(this.__webUiPreferencesPersistTimer);
512
+ this.__webUiPreferencesPersistTimer = 0;
513
+ }
514
+ const pending = this.__webUiPreferencesPendingOverrides;
515
+ if (!pending || typeof pending !== 'object') return;
516
+ this.__webUiPreferencesPendingOverrides = null;
517
+ const snapshot = this.buildWebUiPreferencesSnapshot(pending);
518
+ api('set-web-ui-preferences', { preferences: snapshot }).catch(() => {});
147
519
  },
148
520
 
149
521
  persistWebUiPreferences(overrides = {}) {
150
522
  if (this.__webUiPreferencesApplying) return;
151
- const snapshot = this.buildWebUiPreferencesSnapshot(overrides && typeof overrides === 'object' ? overrides : {});
523
+ this.__webUiPreferencesPendingOverrides = mergePreferenceOverrides(
524
+ this.__webUiPreferencesPendingOverrides,
525
+ overrides && typeof overrides === 'object' ? overrides : {}
526
+ );
152
527
  if (this.__webUiPreferencesPersistTimer) {
153
528
  clearTimeout(this.__webUiPreferencesPersistTimer);
154
529
  }
155
530
  this.__webUiPreferencesPersistTimer = setTimeout(() => {
156
- this.__webUiPreferencesPersistTimer = 0;
157
- api('set-web-ui-preferences', { preferences: snapshot }).catch(() => {});
531
+ this.flushWebUiPreferences();
158
532
  }, 120);
159
533
  }
160
534
  };
@@ -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
-