codexmate 0.0.20 → 0.0.22

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 (96) hide show
  1. package/README.md +289 -152
  2. package/README.zh.md +321 -0
  3. package/cli/agents-files.js +224 -0
  4. package/cli/archive-helpers.js +446 -0
  5. package/cli/auth-profiles.js +359 -0
  6. package/cli/builtin-proxy.js +1044 -0
  7. package/cli/claude-proxy.js +998 -0
  8. package/cli/config-bootstrap.js +384 -0
  9. package/cli/openai-bridge.js +950 -0
  10. package/cli/openclaw-config.js +629 -0
  11. package/cli/session-usage.concurrent.js +28 -0
  12. package/cli/session-usage.js +112 -0
  13. package/cli/session-usage.models.js +176 -0
  14. package/cli/skills.js +1141 -0
  15. package/cli/zip-commands.js +510 -0
  16. package/cli.js +9408 -9719
  17. package/lib/cli-models-utils.js +109 -1
  18. package/lib/cli-path-utils.js +69 -0
  19. package/lib/cli-sessions.js +386 -0
  20. package/lib/download-artifacts.js +77 -0
  21. package/lib/task-orchestrator.js +869 -0
  22. package/package.json +14 -10
  23. package/res/logo.png +0 -0
  24. package/res/vue.global.prod.js +13 -0
  25. package/web-ui/app.js +193 -15
  26. package/web-ui/index.html +5 -1
  27. package/web-ui/logic.agents-diff.mjs +1 -1
  28. package/web-ui/logic.claude.mjs +60 -0
  29. package/web-ui/logic.runtime.mjs +11 -7
  30. package/web-ui/logic.sessions.mjs +372 -21
  31. package/web-ui/modules/api.mjs +22 -1
  32. package/web-ui/modules/app.computed.dashboard.mjs +23 -10
  33. package/web-ui/modules/app.computed.index.mjs +4 -0
  34. package/web-ui/modules/app.computed.main-tabs.mjs +198 -0
  35. package/web-ui/modules/app.computed.session.mjs +521 -9
  36. package/web-ui/modules/app.methods.agents.mjs +62 -11
  37. package/web-ui/modules/app.methods.codex-config.mjs +189 -34
  38. package/web-ui/modules/app.methods.index.mjs +7 -1
  39. package/web-ui/modules/app.methods.install.mjs +24 -20
  40. package/web-ui/modules/app.methods.navigation.mjs +142 -1
  41. package/web-ui/modules/app.methods.openclaw-core.mjs +339 -39
  42. package/web-ui/modules/app.methods.openclaw-editing.mjs +39 -4
  43. package/web-ui/modules/app.methods.openclaw-persist.mjs +122 -4
  44. package/web-ui/modules/app.methods.providers.mjs +192 -53
  45. package/web-ui/modules/app.methods.session-actions.mjs +99 -19
  46. package/web-ui/modules/app.methods.session-browser.mjs +196 -5
  47. package/web-ui/modules/app.methods.session-timeline.mjs +22 -15
  48. package/web-ui/modules/app.methods.session-trash.mjs +3 -0
  49. package/web-ui/modules/app.methods.startup-claude.mjs +70 -71
  50. package/web-ui/modules/app.methods.task-orchestration.mjs +471 -0
  51. package/web-ui/modules/config-mode.computed.mjs +2 -0
  52. package/web-ui/modules/config-template-confirm-pref.mjs +33 -0
  53. package/web-ui/modules/i18n.mjs +1609 -0
  54. package/web-ui/modules/plugins.computed.mjs +220 -0
  55. package/web-ui/modules/plugins.methods.mjs +620 -0
  56. package/web-ui/modules/plugins.storage.mjs +37 -0
  57. package/web-ui/partials/index/layout-footer.html +1 -57
  58. package/web-ui/partials/index/layout-header.html +299 -175
  59. package/web-ui/partials/index/modal-config-template-agents.html +79 -29
  60. package/web-ui/partials/index/modal-confirm-toast.html +1 -1
  61. package/web-ui/partials/index/modal-health-check.html +14 -14
  62. package/web-ui/partials/index/modal-openclaw-config.html +47 -42
  63. package/web-ui/partials/index/modal-skills.html +130 -114
  64. package/web-ui/partials/index/modals-basic.html +71 -102
  65. package/web-ui/partials/index/panel-config-claude.html +50 -12
  66. package/web-ui/partials/index/panel-config-codex.html +34 -37
  67. package/web-ui/partials/index/panel-config-openclaw.html +10 -16
  68. package/web-ui/partials/index/panel-docs.html +147 -0
  69. package/web-ui/partials/index/panel-market.html +38 -38
  70. package/web-ui/partials/index/panel-orchestration.html +397 -0
  71. package/web-ui/partials/index/panel-plugins.html +243 -0
  72. package/web-ui/partials/index/panel-sessions.html +51 -146
  73. package/web-ui/partials/index/panel-settings.html +188 -96
  74. package/web-ui/partials/index/panel-usage.html +353 -0
  75. package/web-ui/session-helpers.mjs +221 -10
  76. package/web-ui/styles/base-theme.css +120 -229
  77. package/web-ui/styles/controls-forms.css +59 -51
  78. package/web-ui/styles/docs-panel.css +247 -0
  79. package/web-ui/styles/layout-shell.css +394 -128
  80. package/web-ui/styles/modals-core.css +18 -3
  81. package/web-ui/styles/navigation-panels.css +184 -183
  82. package/web-ui/styles/plugins-panel.css +518 -0
  83. package/web-ui/styles/responsive.css +102 -62
  84. package/web-ui/styles/sessions-list.css +13 -27
  85. package/web-ui/styles/sessions-preview.css +13 -7
  86. package/web-ui/styles/sessions-toolbar-trash.css +25 -0
  87. package/web-ui/styles/sessions-usage.css +581 -6
  88. package/web-ui/styles/settings-panel.css +166 -0
  89. package/web-ui/styles/skills-list.css +16 -11
  90. package/web-ui/styles/skills-market.css +63 -2
  91. package/web-ui/styles/task-orchestration.css +776 -0
  92. package/web-ui/styles/titles-cards.css +67 -66
  93. package/web-ui/styles.css +4 -0
  94. package/README.en.md +0 -259
  95. package/res/screenshot.png +0 -0
  96. package/res/vue.global.js +0 -18552
@@ -1,3 +1,8 @@
1
+ import {
2
+ normalizeConfigTemplateDiffConfirmEnabled,
3
+ persistConfigTemplateDiffConfirmEnabledToStorage
4
+ } from './config-template-confirm-pref.mjs';
5
+
1
6
  export function createSessionActionMethods(options = {}) {
2
7
  const {
3
8
  api,
@@ -153,6 +158,54 @@ export function createSessionActionMethods(options = {}) {
153
158
  return this.quoteShellArg(value);
154
159
  },
155
160
 
161
+ normalizeShareCommandPrefix(value) {
162
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
163
+ if (normalized === 'codexmate') {
164
+ return 'codexmate';
165
+ }
166
+ return 'npm start';
167
+ },
168
+
169
+ normalizeSessionTrashEnabled(value) {
170
+ if (value === false) return false;
171
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
172
+ if (normalized === '0' || normalized === 'false' || normalized === 'off' || normalized === 'no') {
173
+ return false;
174
+ }
175
+ return true;
176
+ },
177
+
178
+ normalizeConfigTemplateDiffConfirmEnabled(value) {
179
+ return normalizeConfigTemplateDiffConfirmEnabled(value);
180
+ },
181
+
182
+ setSessionTrashEnabled(value) {
183
+ const enabled = this.normalizeSessionTrashEnabled(value);
184
+ this.sessionTrashEnabled = enabled;
185
+ try {
186
+ localStorage.setItem('codexmateSessionTrashEnabled', enabled ? 'true' : 'false');
187
+ } catch (_) {}
188
+ },
189
+
190
+ setConfigTemplateDiffConfirmEnabled(value) {
191
+ const enabled = this.normalizeConfigTemplateDiffConfirmEnabled(value);
192
+ this.configTemplateDiffConfirmEnabled = enabled;
193
+ persistConfigTemplateDiffConfirmEnabledToStorage(enabled);
194
+ },
195
+
196
+ getShareCommandPrefixInvocation() {
197
+ const prefix = this.normalizeShareCommandPrefix(this.shareCommandPrefix);
198
+ return prefix === 'codexmate' ? 'codexmate' : 'npm start';
199
+ },
200
+
201
+ setShareCommandPrefix(value) {
202
+ const normalized = this.normalizeShareCommandPrefix(value);
203
+ this.shareCommandPrefix = normalized;
204
+ try {
205
+ localStorage.setItem('codexmateShareCommandPrefix', normalized);
206
+ } catch (_) {}
207
+ },
208
+
156
209
  fallbackCopyText(text) {
157
210
  let textarea = null;
158
211
  try {
@@ -256,16 +309,19 @@ export function createSessionActionMethods(options = {}) {
256
309
  const baseUrl = typeof payload.baseUrl === 'string' ? payload.baseUrl.trim() : '';
257
310
  const apiKey = typeof payload.apiKey === 'string' ? payload.apiKey : '';
258
311
  const model = typeof payload.model === 'string' ? payload.model.trim() : '';
312
+ const bridge = typeof payload.bridge === 'string' ? payload.bridge.trim() : '';
259
313
  if (!name || !baseUrl) return '';
260
314
 
315
+ const cli = this.getShareCommandPrefixInvocation();
261
316
  const nameArg = this.quoteShellArg(name);
262
317
  const urlArg = this.quoteShellArg(baseUrl);
263
318
  const keyArg = apiKey ? this.quoteShellArg(apiKey) : '';
264
- const switchCmd = `codexmate switch ${nameArg}`;
319
+ const switchCmd = `${cli} switch ${nameArg}`;
320
+ const bridgeArgs = bridge ? ` --bridge ${this.quoteShellArg(bridge)}` : '';
265
321
  const addCmd = apiKey
266
- ? `codexmate add ${nameArg} ${urlArg} ${keyArg}`
267
- : `codexmate add ${nameArg} ${urlArg}`;
268
- const modelCmd = model ? ` && codexmate use ${this.quoteShellArg(model)}` : '';
322
+ ? `${cli} add ${nameArg} ${urlArg} ${keyArg}${bridgeArgs}`
323
+ : `${cli} add ${nameArg} ${urlArg}${bridgeArgs}`;
324
+ const modelCmd = model ? ` && ${cli} use ${this.quoteShellArg(model)}` : '';
269
325
  return `${addCmd} && ${switchCmd}${modelCmd}`;
270
326
  },
271
327
 
@@ -280,7 +336,7 @@ export function createSessionActionMethods(options = {}) {
280
336
  const urlArg = this.quoteShellArg(baseUrl);
281
337
  const keyArg = this.quoteShellArg(apiKey);
282
338
  const modelArg = this.quoteShellArg(model);
283
- return `codexmate claude ${urlArg} ${keyArg} ${modelArg}`;
339
+ return `${this.getShareCommandPrefixInvocation()} claude ${urlArg} ${keyArg} ${modelArg}`;
284
340
  },
285
341
 
286
342
  async copyProviderShareCommand(provider) {
@@ -290,7 +346,7 @@ export function createSessionActionMethods(options = {}) {
290
346
  return;
291
347
  }
292
348
  if (!this.shouldAllowProviderShare(provider)) {
293
- this.showMessage('本地入口不可分享', 'info');
349
+ this.showMessage('不可分享', 'info');
294
350
  return;
295
351
  }
296
352
  if (this.providerShareLoading[name]) {
@@ -389,6 +445,9 @@ export function createSessionActionMethods(options = {}) {
389
445
  }
390
446
 
391
447
  this.showMessage('操作成功', 'success');
448
+ if (typeof this.invalidateSessionsUsageData === 'function') {
449
+ this.invalidateSessionsUsageData({ preserveList: true });
450
+ }
392
451
  try {
393
452
  await this.loadSessions();
394
453
  if (res.sessionId) {
@@ -412,13 +471,27 @@ export function createSessionActionMethods(options = {}) {
412
471
  this.showMessage('不支持此操作', 'error');
413
472
  return;
414
473
  }
474
+ const useTrash = this.sessionTrashEnabled !== false;
475
+ if (!useTrash && typeof this.requestConfirmDialog === 'function') {
476
+ const confirmed = await this.requestConfirmDialog({
477
+ title: '直接删除会话',
478
+ message: '关闭回收站后,删除会话将直接永久删除,且无法恢复。',
479
+ confirmText: '直接删除',
480
+ cancelText: '取消',
481
+ danger: true
482
+ });
483
+ if (!confirmed) {
484
+ return;
485
+ }
486
+ }
415
487
  const key = this.getSessionExportKey(session);
416
488
  if (this.sessionDeleting[key]) {
417
489
  return;
418
490
  }
419
491
  this.sessionDeleting[key] = true;
420
492
  try {
421
- const res = await api('trash-session', {
493
+ const action = useTrash ? 'trash-session' : 'delete-session';
494
+ const res = await api(action, {
422
495
  source: session.source,
423
496
  sessionId: session.sessionId,
424
497
  filePath: session.filePath
@@ -428,19 +501,26 @@ export function createSessionActionMethods(options = {}) {
428
501
  return;
429
502
  }
430
503
  this.removeSessionPin(session);
431
- this.invalidateSessionTrashRequests();
432
- this.showMessage('已移入回收站', 'success');
433
- if (this.sessionTrashLoadedOnce) {
434
- this.prependSessionTrashItem(this.buildSessionTrashItemFromSession(session, res), {
435
- totalCount: res && res.totalCount !== undefined ? res.totalCount : undefined
436
- });
504
+ if (useTrash) {
505
+ this.invalidateSessionTrashRequests();
506
+ this.showMessage('已移入回收站', 'success');
507
+ if (this.sessionTrashLoadedOnce) {
508
+ this.prependSessionTrashItem(this.buildSessionTrashItemFromSession(session, res), {
509
+ totalCount: res && res.totalCount !== undefined ? res.totalCount : undefined
510
+ });
511
+ } else {
512
+ this.sessionTrashTotalCount = this.normalizeSessionTrashTotalCount(
513
+ res && res.totalCount !== undefined
514
+ ? res.totalCount
515
+ : (this.normalizeSessionTrashTotalCount(this.sessionTrashTotalCount, this.sessionTrashItems) + 1),
516
+ this.sessionTrashItems
517
+ );
518
+ }
437
519
  } else {
438
- this.sessionTrashTotalCount = this.normalizeSessionTrashTotalCount(
439
- res && res.totalCount !== undefined
440
- ? res.totalCount
441
- : (this.normalizeSessionTrashTotalCount(this.sessionTrashTotalCount, this.sessionTrashItems) + 1),
442
- this.sessionTrashItems
443
- );
520
+ this.showMessage('已删除', 'success');
521
+ }
522
+ if (typeof this.invalidateSessionsUsageData === 'function') {
523
+ this.invalidateSessionsUsageData({ preserveList: true });
444
524
  }
445
525
  try {
446
526
  await this.removeSessionFromCurrentList(session);
@@ -5,6 +5,69 @@ import {
5
5
  normalizeSessionPathFilter
6
6
  } from '../logic.mjs';
7
7
 
8
+ function isSessionLoadNativeDialogEnabled(vm) {
9
+ if (vm && typeof vm.isSessionLoadNativeDialogEnabled === 'function' && vm.isSessionLoadNativeDialogEnabled !== isSessionLoadNativeDialogEnabled) {
10
+ try {
11
+ return !!vm.isSessionLoadNativeDialogEnabled();
12
+ } catch (_) {
13
+ // fall through to shared detection
14
+ }
15
+ }
16
+
17
+ try {
18
+ if (globalThis && globalThis.__CODEXMATE_SESSION_LOAD_NATIVE_DIALOG__ === true) {
19
+ return true;
20
+ }
21
+ } catch (_) {
22
+ // ignore global flag lookup failures
23
+ }
24
+
25
+ try {
26
+ if (typeof localStorage !== 'undefined' && typeof localStorage.getItem === 'function') {
27
+ const stored = String(localStorage.getItem('codexmateSessionLoadNativeDialog') || '').trim().toLowerCase();
28
+ if (stored === '1' || stored === 'true' || stored === 'yes' || stored === 'on') {
29
+ return true;
30
+ }
31
+ }
32
+ } catch (_) {
33
+ // ignore storage lookup failures
34
+ }
35
+
36
+ try {
37
+ const search = typeof location !== 'undefined' && location && typeof location.search === 'string'
38
+ ? location.search
39
+ : (typeof window !== 'undefined' && window.location && typeof window.location.search === 'string'
40
+ ? window.location.search
41
+ : '');
42
+ if (!search) {
43
+ return false;
44
+ }
45
+ const params = new URLSearchParams(search);
46
+ const value = String(params.get('sessionLoadNativeDialog') || '').trim().toLowerCase();
47
+ return value === '1' || value === 'true' || value === 'yes' || value === 'on';
48
+ } catch (_) {
49
+ return false;
50
+ }
51
+ }
52
+
53
+ function emitSessionLoadNativeDialog(vm, step, details = '') {
54
+ if (!isSessionLoadNativeDialogEnabled(vm)) {
55
+ return;
56
+ }
57
+ const alertFn = typeof globalThis.alert === 'function'
58
+ ? globalThis.alert.bind(globalThis)
59
+ : (typeof window !== 'undefined' && typeof window.alert === 'function'
60
+ ? window.alert.bind(window)
61
+ : null);
62
+ if (!alertFn) {
63
+ return;
64
+ }
65
+ const message = details
66
+ ? `[codexmate][session-load] ${step}\n${details}`
67
+ : `[codexmate][session-load] ${step}`;
68
+ alertFn(message);
69
+ }
70
+
8
71
  export function createSessionBrowserMethods(options = {}) {
9
72
  const {
10
73
  api,
@@ -84,11 +147,20 @@ export function createSessionBrowserMethods(options = {}) {
84
147
  }
85
148
  },
86
149
 
150
+ isSessionLoadNativeDialogEnabled() {
151
+ return isSessionLoadNativeDialogEnabled(this);
152
+ },
153
+
154
+ emitSessionLoadNativeDialog(step, details = '') {
155
+ emitSessionLoadNativeDialog(this, step, details);
156
+ },
157
+
87
158
  async loadSessionPathOptions(options = {}) {
88
159
  const source = options.source === 'claude' ? 'claude' : (options.source === 'all' ? 'all' : 'codex');
89
160
  const forceRefresh = !!options.forceRefresh;
90
161
  const loaded = !!this.sessionPathOptionsLoadedMap[source];
91
162
  if (!forceRefresh && loaded) {
163
+ emitSessionLoadNativeDialog(this, 'loadSessionPathOptions:cache-hit', `source=${source}`);
92
164
  if (source === this.sessionFilterSource) {
93
165
  this.sessionPathOptionsLoading = false;
94
166
  }
@@ -101,6 +173,11 @@ export function createSessionBrowserMethods(options = {}) {
101
173
  const requestSeq = (Number(nextSeqMap[source]) || 0) + 1;
102
174
  nextSeqMap[source] = requestSeq;
103
175
  this.sessionPathRequestSeqMap = nextSeqMap;
176
+ emitSessionLoadNativeDialog(
177
+ this,
178
+ 'loadSessionPathOptions:start',
179
+ `source=${source}\nforceRefresh=${forceRefresh}\nrequestSeq=${requestSeq}`
180
+ );
104
181
  if (source === this.sessionFilterSource) {
105
182
  this.sessionPathOptionsLoading = true;
106
183
  }
@@ -111,6 +188,11 @@ export function createSessionBrowserMethods(options = {}) {
111
188
  forceRefresh
112
189
  });
113
190
  if (requestSeq !== Number(((this.sessionPathRequestSeqMap || {})[source]) || 0)) {
191
+ emitSessionLoadNativeDialog(
192
+ this,
193
+ 'loadSessionPathOptions:stale-response',
194
+ `source=${source}\nrequestSeq=${requestSeq}`
195
+ );
114
196
  return;
115
197
  }
116
198
  if (res && !res.error && Array.isArray(res.paths)) {
@@ -119,8 +201,24 @@ export function createSessionBrowserMethods(options = {}) {
119
201
  ...this.sessionPathOptionsLoadedMap,
120
202
  [source]: true
121
203
  };
204
+ emitSessionLoadNativeDialog(
205
+ this,
206
+ 'loadSessionPathOptions:success',
207
+ `source=${source}\npaths=${res.paths.length}`
208
+ );
209
+ } else if (res && res.error) {
210
+ emitSessionLoadNativeDialog(
211
+ this,
212
+ 'loadSessionPathOptions:error',
213
+ `source=${source}\nerror=${res.error}`
214
+ );
122
215
  }
123
- } catch (_) {
216
+ } catch (error) {
217
+ emitSessionLoadNativeDialog(
218
+ this,
219
+ 'loadSessionPathOptions:exception',
220
+ `source=${source}\nerror=${error && error.message ? error.message : String(error)}`
221
+ );
124
222
  // 路径补全失败不影响会话主流程
125
223
  } finally {
126
224
  if (
@@ -129,6 +227,11 @@ export function createSessionBrowserMethods(options = {}) {
129
227
  ) {
130
228
  this.sessionPathOptionsLoading = false;
131
229
  }
230
+ emitSessionLoadNativeDialog(
231
+ this,
232
+ 'loadSessionPathOptions:complete',
233
+ `source=${source}\nrequestSeq=${requestSeq}`
234
+ );
132
235
  }
133
236
  },
134
237
 
@@ -359,16 +462,94 @@ export function createSessionBrowserMethods(options = {}) {
359
462
  }
360
463
  },
361
464
 
362
- async loadSessions() {
363
- const result = await loadSessionsHelper.call(this, api);
465
+ invalidateSessionsUsageData(options = {}) {
466
+ this.sessionsUsageLoadedOnce = false;
467
+ this.sessionsUsageError = '';
468
+ if (options.preserveList !== true) {
469
+ this.sessionsUsageList = [];
470
+ }
471
+ },
472
+
473
+ async loadSessionsUsage(options = {}) {
474
+ if (this.sessionsUsageLoading) return;
475
+ this.sessionsUsageLoading = true;
476
+ this.sessionsUsageError = '';
477
+ let loadSucceeded = false;
478
+ try {
479
+ const res = await api('list-sessions-usage', {
480
+ source: 'all',
481
+ limit: 2000,
482
+ forceRefresh: !!options.forceRefresh
483
+ });
484
+ if (res.error) {
485
+ this.sessionsUsageError = res.error;
486
+ this.showMessage(res.error, 'error');
487
+ return;
488
+ }
489
+ this.sessionsUsageList = Array.isArray(res.sessions) ? res.sessions : [];
490
+ loadSucceeded = true;
491
+ } catch (e) {
492
+ this.sessionsUsageError = '加载 usage 统计失败';
493
+ this.showMessage('加载 usage 统计失败', 'error');
494
+ } finally {
495
+ this.sessionsUsageLoading = false;
496
+ if (loadSucceeded) {
497
+ this.sessionsUsageLoadedOnce = true;
498
+ }
499
+ }
500
+ },
501
+
502
+ async loadSessions(options = {}) {
503
+ const result = await loadSessionsHelper.call(this, api, options || {});
364
504
  this.pruneSessionPinnedMap(this.sessionsList);
365
505
  return result;
366
506
  },
367
507
 
368
508
  async selectSession(session) {
369
- if (!session) return;
370
- if (this.activeSession && this.getSessionExportKey(this.activeSession) === this.getSessionExportKey(session)) return;
509
+ if (!session) {
510
+ emitSessionLoadNativeDialog(this, 'selectSession:skip-empty');
511
+ return;
512
+ }
513
+ emitSessionLoadNativeDialog(
514
+ this,
515
+ 'selectSession:start',
516
+ `sessionId=${session.sessionId || ''}\nsource=${session.source || ''}`
517
+ );
518
+ const isSameSession = this.activeSession
519
+ && this.getSessionExportKey(this.activeSession) === this.getSessionExportKey(session);
520
+ if (isSameSession) {
521
+ emitSessionLoadNativeDialog(this, 'selectSession:same-session', `sessionId=${session.sessionId || ''}`);
522
+ if (this.sessionDetailLoading) {
523
+ emitSessionLoadNativeDialog(this, 'selectSession:skip-detail-loading', `sessionId=${session.sessionId || ''}`);
524
+ return;
525
+ }
526
+ const currentMessages = Array.isArray(this.activeSessionMessages) ? this.activeSessionMessages : [];
527
+ if (currentMessages.length > 0) {
528
+ emitSessionLoadNativeDialog(
529
+ this,
530
+ 'selectSession:skip-existing-messages',
531
+ `sessionId=${session.sessionId || ''}\nmessages=${currentMessages.length}`
532
+ );
533
+ return;
534
+ }
535
+ if (typeof this.scheduleAfterFrame === 'function') {
536
+ const selectedSession = this.activeSession;
537
+ emitSessionLoadNativeDialog(this, 'selectSession:schedule-detail', `sessionId=${session.sessionId || ''}`);
538
+ this.scheduleAfterFrame(() => {
539
+ if (this.activeSession !== selectedSession) return;
540
+ void this.loadActiveSessionDetail();
541
+ });
542
+ return;
543
+ }
544
+ emitSessionLoadNativeDialog(this, 'selectSession:load-detail-now', `sessionId=${session.sessionId || ''}`);
545
+ await this.loadActiveSessionDetail();
546
+ return;
547
+ }
371
548
  this.activeSession = session;
549
+ emitSessionLoadNativeDialog(this, 'selectSession:activate', `sessionId=${session.sessionId || ''}`);
550
+ if (typeof this.expandVisibleSessionList === 'function') {
551
+ this.expandVisibleSessionList(0, { ensureActive: true });
552
+ }
372
553
  this.activeSessionMessages = [];
373
554
  this.resetSessionDetailPagination();
374
555
  this.resetSessionPreviewMessageRender();
@@ -377,6 +558,16 @@ export function createSessionBrowserMethods(options = {}) {
377
558
  this.cancelSessionTimelineSync();
378
559
  this.sessionTimelineActiveKey = '';
379
560
  this.clearSessionTimelineRefs();
561
+ if (typeof this.scheduleAfterFrame === 'function') {
562
+ const selectedSession = this.activeSession;
563
+ emitSessionLoadNativeDialog(this, 'selectSession:schedule-detail', `sessionId=${session.sessionId || ''}`);
564
+ this.scheduleAfterFrame(() => {
565
+ if (this.activeSession !== selectedSession) return;
566
+ void this.loadActiveSessionDetail();
567
+ });
568
+ return;
569
+ }
570
+ emitSessionLoadNativeDialog(this, 'selectSession:load-detail-now', `sessionId=${session.sessionId || ''}`);
380
571
  await this.loadActiveSessionDetail();
381
572
  },
382
573
 
@@ -22,6 +22,16 @@ export function createSessionTimelineMethods() {
22
22
  };
23
23
 
24
24
  return {
25
+ hasRenderableSessionTimeline() {
26
+ return !!(
27
+ this.sessionTimelineEnabled
28
+ && this.mainTab === 'sessions'
29
+ && this.getMainTabForNav() === 'sessions'
30
+ && this.sessionPreviewRenderEnabled
31
+ && Array.isArray(this.sessionTimelineNodes)
32
+ && this.sessionTimelineNodes.length
33
+ );
34
+ },
25
35
  setSessionPreviewContainerRef(el) {
26
36
  this.sessionPreviewContainerEl = el || null;
27
37
  this.updateSessionTimelineOffset();
@@ -33,21 +43,6 @@ export function createSessionTimelineMethods() {
33
43
  },
34
44
  observeSessionPreviewHeaderResize() {
35
45
  this.disconnectSessionPreviewHeaderResizeObserver();
36
- if (!this.sessionPreviewHeaderEl || typeof ResizeObserver !== 'function') return;
37
- this.sessionPreviewHeaderResizeObserver = new ResizeObserver(() => {
38
- this.updateSessionTimelineOffset();
39
- this.invalidateSessionTimelineMeasurementCache();
40
- if (
41
- this.sessionTimelineEnabled
42
- && this.mainTab === 'sessions'
43
- && this.getMainTabForNav() === 'sessions'
44
- && this.sessionPreviewRenderEnabled
45
- && this.sessionTimelineNodes.length
46
- ) {
47
- this.scheduleSessionTimelineSync();
48
- }
49
- });
50
- this.sessionPreviewHeaderResizeObserver.observe(this.sessionPreviewHeaderEl);
51
46
  },
52
47
  setSessionPreviewHeaderRef(el) {
53
48
  this.disconnectSessionPreviewHeaderResizeObserver();
@@ -191,7 +186,18 @@ export function createSessionTimelineMethods() {
191
186
  updateSessionTimelineOffset() {
192
187
  const container = this.sessionPreviewContainerEl || this.$refs.sessionPreviewContainer;
193
188
  if (!container || !container.style) return;
189
+ if (!this.hasRenderableSessionTimeline()) {
190
+ if (this.__sessionPreviewHeaderOffsetPx != null) {
191
+ container.style.removeProperty('--session-preview-header-offset');
192
+ this.__sessionPreviewHeaderOffsetPx = null;
193
+ }
194
+ return;
195
+ }
194
196
  const offset = getSessionPreviewHeaderOffset(this);
197
+ if (this.__sessionPreviewHeaderOffsetPx === offset) {
198
+ return;
199
+ }
200
+ this.__sessionPreviewHeaderOffsetPx = offset;
195
201
  container.style.setProperty('--session-preview-header-offset', `${offset}px`);
196
202
  },
197
203
  bindSessionMessageRef(messageKey, el, ticket = this.sessionTabRenderTicket) {
@@ -237,6 +243,7 @@ export function createSessionTimelineMethods() {
237
243
  this.sessionTimelineRafId = 0;
238
244
  },
239
245
  scheduleSessionTimelineSync() {
246
+ this.updateSessionTimelineOffset();
240
247
  if (this.sessionTimelineRafId) return;
241
248
  if (typeof requestAnimationFrame === 'function') {
242
249
  this.sessionTimelineRafId = requestAnimationFrame(() => {
@@ -340,6 +340,9 @@ export function createSessionTrashMethods(options = {}) {
340
340
  return;
341
341
  }
342
342
  this.showMessage('会话已恢复', 'success');
343
+ if (typeof this.invalidateSessionsUsageData === 'function') {
344
+ this.invalidateSessionsUsageData({ preserveList: true });
345
+ }
343
346
  this.invalidateSessionTrashRequests();
344
347
  await this.loadSessionTrash({ forceRefresh: true });
345
348
  if (this.sessionsLoadedOnce) {