codexmate 0.0.19 → 0.0.21

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 (102) hide show
  1. package/README.en.md +349 -255
  2. package/README.md +284 -248
  3. package/cli/agents-files.js +162 -0
  4. package/cli/archive-helpers.js +446 -0
  5. package/cli/auth-profiles.js +359 -0
  6. package/cli/builtin-proxy.js +580 -0
  7. package/cli/claude-proxy.js +998 -0
  8. package/cli/config-bootstrap.js +384 -0
  9. package/cli/config-health.js +338 -0
  10. package/cli/openclaw-config.js +629 -0
  11. package/cli/skills.js +1141 -0
  12. package/cli/zip-commands.js +510 -0
  13. package/cli.js +13129 -12973
  14. package/lib/cli-file-utils.js +151 -151
  15. package/lib/cli-models-utils.js +419 -152
  16. package/lib/cli-network-utils.js +164 -148
  17. package/lib/cli-path-utils.js +69 -0
  18. package/lib/cli-session-utils.js +121 -121
  19. package/lib/cli-sessions.js +386 -0
  20. package/lib/cli-utils.js +155 -155
  21. package/lib/download-artifacts.js +77 -0
  22. package/lib/mcp-stdio.js +440 -440
  23. package/lib/task-orchestrator.js +869 -0
  24. package/lib/text-diff.js +303 -303
  25. package/lib/workflow-engine.js +340 -340
  26. package/package.json +74 -63
  27. package/res/json5.min.js +1 -1
  28. package/res/vue.global.prod.js +13 -0
  29. package/web-ui/app.js +530 -5548
  30. package/web-ui/index.html +33 -2246
  31. package/web-ui/logic.agents-diff.mjs +386 -0
  32. package/web-ui/logic.claude.mjs +168 -0
  33. package/web-ui/logic.mjs +5 -793
  34. package/web-ui/logic.runtime.mjs +124 -0
  35. package/web-ui/logic.sessions.mjs +581 -0
  36. package/web-ui/modules/api.mjs +90 -0
  37. package/web-ui/modules/app.computed.dashboard.mjs +113 -0
  38. package/web-ui/modules/app.computed.index.mjs +15 -0
  39. package/web-ui/modules/app.computed.main-tabs.mjs +195 -0
  40. package/web-ui/modules/app.computed.session.mjs +507 -0
  41. package/web-ui/modules/app.constants.mjs +15 -0
  42. package/web-ui/modules/app.methods.agents.mjs +493 -0
  43. package/web-ui/modules/app.methods.claude-config.mjs +174 -0
  44. package/web-ui/modules/app.methods.codex-config.mjs +640 -0
  45. package/web-ui/modules/app.methods.index.mjs +88 -0
  46. package/web-ui/modules/app.methods.install.mjs +149 -0
  47. package/web-ui/modules/app.methods.navigation.mjs +619 -0
  48. package/web-ui/modules/app.methods.openclaw-core.mjs +814 -0
  49. package/web-ui/modules/app.methods.openclaw-editing.mjs +372 -0
  50. package/web-ui/modules/app.methods.openclaw-persist.mjs +369 -0
  51. package/web-ui/modules/app.methods.providers.mjs +363 -0
  52. package/web-ui/modules/app.methods.runtime.mjs +323 -0
  53. package/web-ui/modules/app.methods.session-actions.mjs +520 -0
  54. package/web-ui/modules/app.methods.session-browser.mjs +626 -0
  55. package/web-ui/modules/app.methods.session-timeline.mjs +448 -0
  56. package/web-ui/modules/app.methods.session-trash.mjs +422 -0
  57. package/web-ui/modules/app.methods.startup-claude.mjs +412 -0
  58. package/web-ui/modules/app.methods.task-orchestration.mjs +471 -0
  59. package/web-ui/modules/config-mode.computed.mjs +126 -124
  60. package/web-ui/modules/skills.computed.mjs +107 -107
  61. package/web-ui/modules/skills.methods.mjs +481 -481
  62. package/web-ui/partials/index/layout-footer.html +13 -0
  63. package/web-ui/partials/index/layout-header.html +402 -0
  64. package/web-ui/partials/index/modal-config-template-agents.html +125 -0
  65. package/web-ui/partials/index/modal-confirm-toast.html +32 -0
  66. package/web-ui/partials/index/modal-health-check.html +72 -0
  67. package/web-ui/partials/index/modal-openclaw-config.html +280 -0
  68. package/web-ui/partials/index/modal-skills.html +184 -0
  69. package/web-ui/partials/index/modals-basic.html +156 -0
  70. package/web-ui/partials/index/panel-config-claude.html +126 -0
  71. package/web-ui/partials/index/panel-config-codex.html +237 -0
  72. package/web-ui/partials/index/panel-config-openclaw.html +78 -0
  73. package/web-ui/partials/index/panel-docs.html +130 -0
  74. package/web-ui/partials/index/panel-market.html +174 -0
  75. package/web-ui/partials/index/panel-orchestration.html +397 -0
  76. package/web-ui/partials/index/panel-sessions.html +292 -0
  77. package/web-ui/partials/index/panel-settings.html +190 -0
  78. package/web-ui/partials/index/panel-usage.html +213 -0
  79. package/web-ui/session-helpers.mjs +559 -362
  80. package/web-ui/source-bundle.cjs +233 -0
  81. package/web-ui/styles/base-theme.css +271 -0
  82. package/web-ui/styles/controls-forms.css +360 -0
  83. package/web-ui/styles/docs-panel.css +182 -0
  84. package/web-ui/styles/feedback.css +108 -0
  85. package/web-ui/styles/health-check-dialog.css +144 -0
  86. package/web-ui/styles/layout-shell.css +376 -0
  87. package/web-ui/styles/modals-core.css +464 -0
  88. package/web-ui/styles/navigation-panels.css +348 -0
  89. package/web-ui/styles/openclaw-structured.css +266 -0
  90. package/web-ui/styles/responsive.css +450 -0
  91. package/web-ui/styles/sessions-list.css +400 -0
  92. package/web-ui/styles/sessions-preview.css +411 -0
  93. package/web-ui/styles/sessions-toolbar-trash.css +243 -0
  94. package/web-ui/styles/sessions-usage.css +628 -0
  95. package/web-ui/styles/skills-list.css +296 -0
  96. package/web-ui/styles/skills-market.css +335 -0
  97. package/web-ui/styles/task-orchestration.css +776 -0
  98. package/web-ui/styles/titles-cards.css +408 -0
  99. package/web-ui/styles.css +18 -4668
  100. package/web-ui.html +17 -17
  101. package/res/screenshot.png +0 -0
  102. package/res/vue.global.js +0 -18552
@@ -1,362 +1,559 @@
1
- import { buildSessionListParams } from './logic.mjs';
2
-
3
- function clearSessionTimelineRefs(vm) {
4
- if (typeof vm.clearSessionTimelineRefs === 'function') {
5
- vm.clearSessionTimelineRefs();
6
- return;
7
- }
8
- vm.sessionMessageRefMap = Object.create(null);
9
- if (vm && typeof vm === 'object' && Object.prototype.hasOwnProperty.call(vm, 'sessionMessageRefBinderMap')) {
10
- vm.sessionMessageRefBinderMap = Object.create(null);
11
- }
12
- }
13
-
14
- export function switchMainTab(tab) {
15
- const nextTab = typeof tab === 'string' ? tab : '';
16
- const previousTab = this.mainTab;
17
- const leavingSessions = previousTab === 'sessions' && nextTab !== 'sessions';
18
- this.mainTab = nextTab;
19
-
20
- if (leavingSessions) {
21
- const teardown = () => {
22
- if (this.mainTab === 'sessions') return;
23
- if (typeof this.finalizeSessionTabTeardown === 'function') {
24
- if (typeof this.suspendSessionTabRender === 'function') {
25
- this.suspendSessionTabRender();
26
- }
27
- this.finalizeSessionTabTeardown();
28
- return;
29
- }
30
- if (typeof this.teardownSessionTabRender === 'function') {
31
- this.teardownSessionTabRender();
32
- }
33
- };
34
- if (typeof this.scheduleSessionTabDeferredTeardown === 'function') {
35
- this.scheduleSessionTabDeferredTeardown(teardown);
36
- } else if (typeof this.scheduleAfterFrame === 'function') {
37
- this.scheduleAfterFrame(teardown);
38
- } else {
39
- teardown();
40
- }
41
- }
42
-
43
- if (nextTab === 'sessions' && !this.sessionsLoadedOnce) {
44
- this.loadSessions();
45
- }
46
- if (nextTab === 'sessions') {
47
- this.prepareSessionTabRender();
48
- }
49
- const shouldLoadTrashListOnSettingsEnter = nextTab === 'settings'
50
- && this.settingsTab === 'trash'
51
- && typeof this.loadSessionTrash === 'function';
52
- if (shouldLoadTrashListOnSettingsEnter) {
53
- this.loadSessionTrash({
54
- forceRefresh: !!this.sessionTrashLoadedOnce
55
- });
56
- }
57
- const shouldPrimeTrashCountOnSettingsEnter = nextTab === 'settings'
58
- && this.settingsTab !== 'trash'
59
- && typeof this.loadSessionTrashCount === 'function';
60
- if (shouldPrimeTrashCountOnSettingsEnter) {
61
- this.sessionTrashLoadedOnce = false;
62
- this.loadSessionTrashCount({ silent: true });
63
- }
64
- const shouldLoadSkillsMarketOnEnter = nextTab === 'market'
65
- && previousTab !== 'market'
66
- && typeof this.loadSkillsMarketOverview === 'function';
67
- if (shouldLoadSkillsMarketOnEnter) {
68
- let marketOverviewLoad = null;
69
- try {
70
- marketOverviewLoad = this.loadSkillsMarketOverview({ silent: true });
71
- } catch (_) {
72
- marketOverviewLoad = null;
73
- }
74
- void Promise.resolve(marketOverviewLoad).catch(() => {});
75
- }
76
- if (nextTab === 'config' && this.configMode === 'claude') {
77
- const expectedTab = nextTab;
78
- const expectedConfigMode = this.configMode;
79
- const refresh = () => {
80
- if (this.mainTab !== expectedTab || this.configMode !== expectedConfigMode) return;
81
- this.refreshClaudeModelContext();
82
- };
83
- if (typeof this.scheduleAfterFrame === 'function') {
84
- this.scheduleAfterFrame(refresh);
85
- } else {
86
- refresh();
87
- }
88
- }
89
- }
90
-
91
- export async function loadSessions(api) {
92
- if (this.sessionsLoading) return;
93
- this.sessionsLoading = true;
94
- this.activeSessionDetailError = '';
95
- let loadSucceeded = false;
96
- const params = buildSessionListParams({
97
- source: this.sessionFilterSource,
98
- pathFilter: this.sessionPathFilter,
99
- query: this.sessionQuery,
100
- roleFilter: this.sessionRoleFilter,
101
- timeRangePreset: this.sessionTimePreset
102
- });
103
- try {
104
- const res = await api('list-sessions', params);
105
- if (res.error) {
106
- this.showMessage(res.error, 'error');
107
- this.sessionsList = [];
108
- this.activeSession = null;
109
- this.activeSessionMessages = [];
110
- this.resetSessionDetailPagination();
111
- this.resetSessionPreviewMessageRender();
112
- this.activeSessionDetailClipped = false;
113
- this.cancelSessionTimelineSync();
114
- this.sessionTimelineActiveKey = '';
115
- clearSessionTimelineRefs(this);
116
- } else {
117
- loadSucceeded = true;
118
- this.sessionsList = Array.isArray(res.sessions) ? res.sessions : [];
119
- this.syncSessionPathOptionsForSource(
120
- this.sessionFilterSource,
121
- this.extractPathOptionsFromSessions(this.sessionsList),
122
- true
123
- );
124
- if (this.sessionsList.length === 0) {
125
- this.activeSession = null;
126
- this.activeSessionMessages = [];
127
- this.resetSessionDetailPagination();
128
- this.resetSessionPreviewMessageRender();
129
- this.activeSessionDetailClipped = false;
130
- this.cancelSessionTimelineSync();
131
- this.sessionTimelineActiveKey = '';
132
- clearSessionTimelineRefs(this);
133
- } else {
134
- const oldKey = this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
135
- const matched = this.sessionsList.find(item => this.getSessionExportKey(item) === oldKey);
136
- this.activeSession = matched || this.sessionsList[0];
137
- this.activeSessionMessages = [];
138
- this.resetSessionDetailPagination();
139
- this.resetSessionPreviewMessageRender();
140
- this.activeSessionDetailError = '';
141
- this.activeSessionDetailClipped = false;
142
- this.cancelSessionTimelineSync();
143
- this.sessionTimelineActiveKey = '';
144
- clearSessionTimelineRefs(this);
145
- await this.loadActiveSessionDetail();
146
- }
147
- void this.loadSessionPathOptions({ source: this.sessionFilterSource });
148
- }
149
- } catch (e) {
150
- this.sessionsList = [];
151
- this.activeSession = null;
152
- this.activeSessionMessages = [];
153
- this.resetSessionDetailPagination();
154
- this.resetSessionPreviewMessageRender();
155
- this.activeSessionDetailClipped = false;
156
- this.cancelSessionTimelineSync();
157
- this.sessionTimelineActiveKey = '';
158
- clearSessionTimelineRefs(this);
159
- this.showMessage('加载会话失败', 'error');
160
- } finally {
161
- this.sessionsLoading = false;
162
- if (loadSucceeded) {
163
- this.sessionsLoadedOnce = true;
164
- }
165
- }
166
- }
167
-
168
- export async function loadActiveSessionDetail(api, options = {}) {
169
- if (!this.activeSession) {
170
- this.activeSessionMessages = [];
171
- this.resetSessionDetailPagination();
172
- this.resetSessionPreviewMessageRender();
173
- this.activeSessionDetailError = '';
174
- this.activeSessionDetailClipped = false;
175
- this.cancelSessionTimelineSync();
176
- this.sessionTimelineActiveKey = '';
177
- clearSessionTimelineRefs(this);
178
- return;
179
- }
180
-
181
- const currentActiveSession = this.activeSession;
182
- const requestSeq = ++this.sessionDetailRequestSeq;
183
- this.sessionDetailLoading = true;
184
- this.activeSessionDetailError = '';
185
- const fallbackLimit = Number.isFinite(this.sessionDetailInitialMessageLimit)
186
- ? Math.max(1, Math.floor(this.sessionDetailInitialMessageLimit))
187
- : 80;
188
- const rawLimit = Number(this.sessionDetailMessageLimit);
189
- const messageLimit = Number.isFinite(rawLimit)
190
- ? Math.max(1, Math.floor(rawLimit))
191
- : fallbackLimit;
192
- try {
193
- const res = await api('session-detail', {
194
- source: this.activeSession.source,
195
- sessionId: this.activeSession.sessionId,
196
- filePath: this.activeSession.filePath,
197
- messageLimit
198
- });
199
-
200
- if (requestSeq !== this.sessionDetailRequestSeq) {
201
- return;
202
- }
203
- if (!this.activeSession || this.activeSession !== currentActiveSession) {
204
- return;
205
- }
206
-
207
- if (res.error) {
208
- this.activeSessionMessages = [];
209
- this.resetSessionPreviewMessageRender();
210
- this.activeSessionDetailClipped = false;
211
- this.activeSessionDetailError = res.error;
212
- this.cancelSessionTimelineSync();
213
- this.sessionTimelineActiveKey = '';
214
- clearSessionTimelineRefs(this);
215
- return;
216
- }
217
-
218
- const rawMessages = Array.isArray(res.messages) ? res.messages : [];
219
- const normalizedMessages = rawMessages.map((message) => Object.freeze(this.normalizeSessionMessage(message)));
220
- this.activeSessionMessages = Object.freeze(normalizedMessages);
221
- if (typeof this.invalidateSessionTimelineMeasurementCache === 'function') {
222
- this.invalidateSessionTimelineMeasurementCache(true);
223
- }
224
- this.activeSessionDetailClipped = !!res.clipped;
225
- const responseLimitRaw = Number(res.messageLimit);
226
- this.sessionDetailMessageLimit = Number.isFinite(responseLimitRaw)
227
- ? Math.max(1, Math.floor(responseLimitRaw))
228
- : messageLimit;
229
- if (res.sourceLabel) {
230
- this.activeSession.sourceLabel = res.sourceLabel;
231
- }
232
- if (res.sessionId) {
233
- this.activeSession.sessionId = res.sessionId;
234
- if (!this.activeSession.title) {
235
- this.activeSession.title = res.sessionId;
236
- }
237
- }
238
- if (res.filePath) {
239
- this.activeSession.filePath = res.filePath;
240
- }
241
- if (res.updatedAt) {
242
- this.activeSession.updatedAt = res.updatedAt;
243
- }
244
- if (res.cwd) {
245
- this.activeSession.cwd = res.cwd;
246
- }
247
- if (Number.isFinite(res.totalMessages)) {
248
- this.syncActiveSessionMessageCount(res.totalMessages);
249
- }
250
- if (this.mainTab === 'sessions' && this.sessionPreviewRenderEnabled) {
251
- const preserveVisibleCount = !!options.preserveVisibleCount;
252
- const pendingVisibleRaw = Number(this.sessionPreviewPendingVisibleCount);
253
- const pendingVisible = Number.isFinite(pendingVisibleRaw)
254
- ? Math.max(0, Math.floor(pendingVisibleRaw))
255
- : 0;
256
- if (preserveVisibleCount && pendingVisible > 0) {
257
- this.sessionPreviewVisibleCount = Math.min(pendingVisible, this.activeSessionMessages.length);
258
- } else {
259
- this.primeSessionPreviewMessageRender();
260
- }
261
- }
262
- this.sessionPreviewPendingVisibleCount = 0;
263
- this.$nextTick(() => {
264
- if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
265
- return;
266
- }
267
- this.updateSessionTimelineOffset();
268
- if (this.sessionTimelineEnabled) {
269
- this.scheduleSessionTimelineSync();
270
- }
271
- });
272
- } catch (e) {
273
- if (requestSeq !== this.sessionDetailRequestSeq) {
274
- return;
275
- }
276
- if (!this.activeSession || this.activeSession !== currentActiveSession) {
277
- return;
278
- }
279
- this.activeSessionMessages = [];
280
- this.sessionPreviewPendingVisibleCount = 0;
281
- this.resetSessionPreviewMessageRender();
282
- this.activeSessionDetailClipped = false;
283
- this.activeSessionDetailError = '加载会话内容失败: ' + e.message;
284
- this.cancelSessionTimelineSync();
285
- this.sessionTimelineActiveKey = '';
286
- clearSessionTimelineRefs(this);
287
- } finally {
288
- if (requestSeq === this.sessionDetailRequestSeq) {
289
- this.sessionDetailLoading = false;
290
- }
291
- }
292
- }
293
-
294
- export async function loadMoreSessionMessages(stepSize) {
295
- if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
296
- return;
297
- }
298
- const total = Array.isArray(this.activeSessionMessages)
299
- ? this.activeSessionMessages.length
300
- : 0;
301
- if (total <= 0) {
302
- this.sessionPreviewVisibleCount = 0;
303
- return;
304
- }
305
- const step = Number.isFinite(stepSize)
306
- ? Math.max(1, Math.floor(stepSize))
307
- : (Number.isFinite(this.sessionPreviewLoadStep)
308
- ? Math.max(1, Math.floor(this.sessionPreviewLoadStep))
309
- : 40);
310
- const current = Number.isFinite(this.sessionPreviewVisibleCount)
311
- ? Math.max(0, Math.floor(this.sessionPreviewVisibleCount))
312
- : 0;
313
- const targetVisible = current + step;
314
- if (targetVisible <= total) {
315
- this.sessionPreviewVisibleCount = Math.min(total, targetVisible);
316
- return;
317
- }
318
-
319
- this.sessionPreviewVisibleCount = total;
320
- if (this.sessionDetailLoading) {
321
- return;
322
- }
323
-
324
- const totalKnownRaw = Number(this.activeSession && this.activeSession.messageCount);
325
- const totalKnown = Number.isFinite(totalKnownRaw)
326
- ? Math.max(0, Math.floor(totalKnownRaw))
327
- : 0;
328
- const hasMoreOnDisk = this.activeSessionDetailClipped || (totalKnown > total);
329
- if (!hasMoreOnDisk) {
330
- return;
331
- }
332
-
333
- const currentLimitRaw = Number(this.sessionDetailMessageLimit);
334
- const currentLimit = Number.isFinite(currentLimitRaw)
335
- ? Math.max(1, Math.floor(currentLimitRaw))
336
- : Math.max(1, total);
337
- const fetchStep = Number.isFinite(this.sessionDetailFetchStep)
338
- ? Math.max(1, Math.floor(this.sessionDetailFetchStep))
339
- : 80;
340
- const limitCapRaw = Number(this.sessionDetailMessageLimitCap);
341
- const limitCap = Number.isFinite(limitCapRaw)
342
- ? Math.max(1, Math.floor(limitCapRaw))
343
- : 1000;
344
-
345
- let nextLimit = Math.max(currentLimit + fetchStep, targetVisible);
346
- if (totalKnown > 0) {
347
- nextLimit = Math.min(nextLimit, totalKnown);
348
- }
349
- nextLimit = Math.min(nextLimit, limitCap);
350
- if (nextLimit <= currentLimit) {
351
- return;
352
- }
353
-
354
- this.sessionPreviewPendingVisibleCount = targetVisible;
355
- this.sessionDetailMessageLimit = nextLimit;
356
- this.sessionPreviewLoadingMore = true;
357
- try {
358
- await this.loadActiveSessionDetail({ preserveVisibleCount: true });
359
- } finally {
360
- this.sessionPreviewLoadingMore = false;
361
- }
362
- }
1
+ import { buildSessionListParams } from './logic.mjs';
2
+
3
+ function clearSessionTimelineRefs(vm) {
4
+ if (typeof vm.clearSessionTimelineRefs === 'function') {
5
+ vm.clearSessionTimelineRefs();
6
+ return;
7
+ }
8
+ vm.sessionMessageRefMap = Object.create(null);
9
+ if (vm && typeof vm === 'object' && Object.prototype.hasOwnProperty.call(vm, 'sessionMessageRefBinderMap')) {
10
+ vm.sessionMessageRefBinderMap = Object.create(null);
11
+ }
12
+ }
13
+
14
+ function hasOwnOption(options, key) {
15
+ return !!options && typeof options === 'object' && Object.prototype.hasOwnProperty.call(options, key);
16
+ }
17
+
18
+ function normalizeSessionLoadOptions(options = {}) {
19
+ const normalized = options && typeof options === 'object' ? options : {};
20
+ const hasIncludeActiveDetail = hasOwnOption(normalized, 'includeActiveDetail');
21
+ return {
22
+ hasIncludeActiveDetail,
23
+ includeActiveDetail: hasIncludeActiveDetail
24
+ ? normalized.includeActiveDetail !== false
25
+ : false,
26
+ forceRefresh: !!normalized.forceRefresh
27
+ };
28
+ }
29
+
30
+ function mergeSessionLoadOptions(baseOptions = {}, nextOptions = {}) {
31
+ const base = normalizeSessionLoadOptions(baseOptions);
32
+ const next = normalizeSessionLoadOptions(nextOptions);
33
+ return {
34
+ hasIncludeActiveDetail: base.hasIncludeActiveDetail || next.hasIncludeActiveDetail,
35
+ includeActiveDetail: (base.hasIncludeActiveDetail && base.includeActiveDetail)
36
+ || (next.hasIncludeActiveDetail && next.includeActiveDetail),
37
+ forceRefresh: base.forceRefresh || next.forceRefresh
38
+ };
39
+ }
40
+
41
+ function shouldIncludeActiveSessionDetail(vm, options = {}) {
42
+ if (options.hasIncludeActiveDetail) {
43
+ return options.includeActiveDetail;
44
+ }
45
+ return vm.mainTab === 'sessions' || !!vm.sessionStandalone;
46
+ }
47
+
48
+ function emitSessionLoadDebug(vm, step, details = '') {
49
+ if (!vm || typeof vm.emitSessionLoadNativeDialog !== 'function') {
50
+ return;
51
+ }
52
+ vm.emitSessionLoadNativeDialog(step, details);
53
+ }
54
+
55
+ function scheduleSessionDetailHydration(vm, options = {}) {
56
+ if (!vm || typeof vm.loadActiveSessionDetail !== 'function') {
57
+ return;
58
+ }
59
+ const hydrationTicket = (Number(vm.__sessionDetailHydrationTicket) || 0) + 1;
60
+ vm.__sessionDetailHydrationTicket = hydrationTicket;
61
+ const task = () => {
62
+ if (hydrationTicket !== Number(vm.__sessionDetailHydrationTicket || 0)) return;
63
+ if (!vm.activeSession) return;
64
+ if (vm.mainTab !== 'sessions' && !vm.sessionStandalone) return;
65
+ if (vm.sessionDetailLoading) return;
66
+ const currentMessages = Array.isArray(vm.activeSessionMessages) ? vm.activeSessionMessages : [];
67
+ if (!options.force && currentMessages.length > 0) {
68
+ emitSessionLoadDebug(vm, 'scheduleSessionDetailHydration:skip-existing-messages', `messages=${currentMessages.length}`);
69
+ return;
70
+ }
71
+ emitSessionLoadDebug(vm, 'scheduleSessionDetailHydration:run', `sessionId=${vm.activeSession && vm.activeSession.sessionId ? vm.activeSession.sessionId : ''}`);
72
+ void vm.loadActiveSessionDetail(options);
73
+ };
74
+ if (typeof vm.scheduleAfterFrame === 'function') {
75
+ emitSessionLoadDebug(vm, 'scheduleSessionDetailHydration:queued');
76
+ vm.scheduleAfterFrame(task);
77
+ return;
78
+ }
79
+ task();
80
+ }
81
+
82
+ export function switchMainTab(tab) {
83
+ const nextTab = typeof tab === 'string' ? tab : '';
84
+ const previousTab = this.mainTab;
85
+ const leavingSessions = previousTab === 'sessions' && nextTab !== 'sessions';
86
+ const enteringSessionsTab = nextTab === 'sessions';
87
+ const enteringUsageTab = nextTab === 'usage';
88
+ const enteringOrchestrationTab = nextTab === 'orchestration';
89
+ emitSessionLoadDebug(this, 'switchMainTab:start', `from=${previousTab}\nto=${nextTab}`);
90
+ this.mainTab = nextTab;
91
+
92
+ if (leavingSessions) {
93
+ const teardown = () => {
94
+ if (this.mainTab === 'sessions') return;
95
+ if (typeof this.finalizeSessionTabTeardown === 'function') {
96
+ if (typeof this.suspendSessionTabRender === 'function') {
97
+ this.suspendSessionTabRender();
98
+ }
99
+ this.finalizeSessionTabTeardown();
100
+ return;
101
+ }
102
+ if (typeof this.teardownSessionTabRender === 'function') {
103
+ this.teardownSessionTabRender();
104
+ }
105
+ };
106
+ if (typeof this.scheduleSessionTabDeferredTeardown === 'function') {
107
+ this.scheduleSessionTabDeferredTeardown(teardown);
108
+ } else if (typeof this.scheduleAfterFrame === 'function') {
109
+ this.scheduleAfterFrame(teardown);
110
+ } else {
111
+ teardown();
112
+ }
113
+ }
114
+
115
+ if (enteringSessionsTab && !this.sessionsLoadedOnce) {
116
+ const canStageInitialSessionDetail = typeof this.scheduleAfterFrame === 'function';
117
+ emitSessionLoadDebug(
118
+ this,
119
+ 'switchMainTab:enter-sessions',
120
+ `sessionsLoadedOnce=${!!this.sessionsLoadedOnce}\nstagedDetail=${canStageInitialSessionDetail}`
121
+ );
122
+ const loadResult = this.loadSessions({
123
+ includeActiveDetail: !canStageInitialSessionDetail
124
+ });
125
+ if (canStageInitialSessionDetail) {
126
+ void Promise.resolve(loadResult)
127
+ .then(() => {
128
+ emitSessionLoadDebug(this, 'switchMainTab:loadSessions-resolved');
129
+ scheduleSessionDetailHydration(this);
130
+ })
131
+ .catch((error) => {
132
+ emitSessionLoadDebug(
133
+ this,
134
+ 'switchMainTab:loadSessions-rejected',
135
+ error && error.message ? error.message : String(error)
136
+ );
137
+ });
138
+ }
139
+ }
140
+ if (enteringUsageTab && !this.sessionsUsageLoadedOnce && typeof this.loadSessionsUsage === 'function') {
141
+ this.loadSessionsUsage();
142
+ }
143
+ if (enteringOrchestrationTab && typeof this.loadTaskOrchestrationOverview === 'function') {
144
+ let orchestrationOverviewLoad = null;
145
+ try {
146
+ orchestrationOverviewLoad = this.loadTaskOrchestrationOverview({
147
+ silent: true,
148
+ includeDetail: true
149
+ });
150
+ } catch (_) {
151
+ orchestrationOverviewLoad = null;
152
+ }
153
+ void Promise.resolve(orchestrationOverviewLoad).catch(() => {});
154
+ }
155
+ if (nextTab !== 'orchestration' && typeof this.stopTaskOrchestrationPolling === 'function') {
156
+ this.stopTaskOrchestrationPolling();
157
+ }
158
+ if (nextTab === 'sessions') {
159
+ this.prepareSessionTabRender();
160
+ }
161
+ const shouldLoadTrashListOnSettingsEnter = nextTab === 'settings'
162
+ && this.settingsTab === 'trash'
163
+ && typeof this.loadSessionTrash === 'function';
164
+ if (shouldLoadTrashListOnSettingsEnter) {
165
+ this.loadSessionTrash({
166
+ forceRefresh: !!this.sessionTrashLoadedOnce
167
+ });
168
+ }
169
+ const shouldPrimeTrashCountOnSettingsEnter = nextTab === 'settings'
170
+ && this.settingsTab !== 'trash'
171
+ && typeof this.loadSessionTrashCount === 'function';
172
+ if (shouldPrimeTrashCountOnSettingsEnter) {
173
+ this.sessionTrashLoadedOnce = false;
174
+ this.loadSessionTrashCount({ silent: true });
175
+ }
176
+ const shouldLoadSkillsMarketOnEnter = nextTab === 'market'
177
+ && previousTab !== 'market'
178
+ && typeof this.loadSkillsMarketOverview === 'function';
179
+ if (shouldLoadSkillsMarketOnEnter) {
180
+ let marketOverviewLoad = null;
181
+ try {
182
+ marketOverviewLoad = this.loadSkillsMarketOverview({ silent: true });
183
+ } catch (_) {
184
+ marketOverviewLoad = null;
185
+ }
186
+ void Promise.resolve(marketOverviewLoad).catch(() => {});
187
+ }
188
+ if (nextTab === 'config' && this.configMode === 'claude') {
189
+ const expectedTab = nextTab;
190
+ const expectedConfigMode = this.configMode;
191
+ const refresh = () => {
192
+ if (this.mainTab !== expectedTab || this.configMode !== expectedConfigMode) return;
193
+ this.refreshClaudeModelContext();
194
+ };
195
+ if (typeof this.scheduleAfterFrame === 'function') {
196
+ this.scheduleAfterFrame(refresh);
197
+ } else {
198
+ refresh();
199
+ }
200
+ }
201
+ }
202
+
203
+ export async function loadSessions(api, options = {}) {
204
+ if (this.sessionsLoading) {
205
+ this.__sessionPendingLoadOptions = mergeSessionLoadOptions(
206
+ this.__sessionPendingLoadOptions,
207
+ options
208
+ );
209
+ emitSessionLoadDebug(this, 'loadSessions:queued-while-busy');
210
+ return;
211
+ }
212
+ const normalizedOptions = normalizeSessionLoadOptions(options);
213
+ const includeActiveDetail = shouldIncludeActiveSessionDetail(this, normalizedOptions);
214
+ this.sessionsLoading = true;
215
+ this.activeSessionDetailError = '';
216
+ let loadSucceeded = false;
217
+ const params = buildSessionListParams({
218
+ source: this.sessionFilterSource,
219
+ pathFilter: this.sessionPathFilter,
220
+ query: this.sessionQuery,
221
+ roleFilter: this.sessionRoleFilter,
222
+ timeRangePreset: this.sessionTimePreset,
223
+ forceRefresh: normalizedOptions.forceRefresh
224
+ });
225
+ emitSessionLoadDebug(
226
+ this,
227
+ 'loadSessions:start',
228
+ `source=${params.source || ''}\nforceRefresh=${!!params.forceRefresh}\nincludeActiveDetail=${includeActiveDetail}`
229
+ );
230
+ let pendingOptions = null;
231
+ try {
232
+ const res = await api('list-sessions', params);
233
+ if (res.error) {
234
+ emitSessionLoadDebug(this, 'loadSessions:error-response', `error=${res.error}`);
235
+ this.showMessage(res.error, 'error');
236
+ this.sessionsList = [];
237
+ if (typeof this.primeSessionListRender === 'function') {
238
+ this.primeSessionListRender();
239
+ }
240
+ this.activeSession = null;
241
+ this.activeSessionMessages = [];
242
+ this.resetSessionDetailPagination();
243
+ this.resetSessionPreviewMessageRender();
244
+ this.activeSessionDetailClipped = false;
245
+ this.cancelSessionTimelineSync();
246
+ this.sessionTimelineActiveKey = '';
247
+ clearSessionTimelineRefs(this);
248
+ } else {
249
+ loadSucceeded = true;
250
+ this.sessionsList = Array.isArray(res.sessions) ? res.sessions : [];
251
+ emitSessionLoadDebug(this, 'loadSessions:response', `sessions=${this.sessionsList.length}`);
252
+ if (typeof this.primeSessionListRender === 'function') {
253
+ this.primeSessionListRender();
254
+ }
255
+ this.syncSessionPathOptionsForSource(
256
+ this.sessionFilterSource,
257
+ this.extractPathOptionsFromSessions(this.sessionsList),
258
+ true
259
+ );
260
+ if (this.sessionsList.length === 0) {
261
+ emitSessionLoadDebug(this, 'loadSessions:empty');
262
+ this.activeSession = null;
263
+ this.activeSessionMessages = [];
264
+ this.resetSessionDetailPagination();
265
+ this.resetSessionPreviewMessageRender();
266
+ this.activeSessionDetailClipped = false;
267
+ this.cancelSessionTimelineSync();
268
+ this.sessionTimelineActiveKey = '';
269
+ clearSessionTimelineRefs(this);
270
+ } else {
271
+ const oldKey = this.activeSession ? this.getSessionExportKey(this.activeSession) : '';
272
+ const matched = this.sessionsList.find(item => this.getSessionExportKey(item) === oldKey);
273
+ this.activeSession = matched || this.sessionsList[0];
274
+ emitSessionLoadDebug(
275
+ this,
276
+ 'loadSessions:active-session-selected',
277
+ `sessionId=${this.activeSession && this.activeSession.sessionId ? this.activeSession.sessionId : ''}`
278
+ );
279
+ this.activeSessionMessages = [];
280
+ this.resetSessionDetailPagination();
281
+ this.resetSessionPreviewMessageRender();
282
+ this.activeSessionDetailError = '';
283
+ this.activeSessionDetailClipped = false;
284
+ this.cancelSessionTimelineSync();
285
+ this.sessionTimelineActiveKey = '';
286
+ clearSessionTimelineRefs(this);
287
+ if (includeActiveDetail) {
288
+ emitSessionLoadDebug(
289
+ this,
290
+ 'loadSessions:hydrate-active-detail',
291
+ `sessionId=${this.activeSession && this.activeSession.sessionId ? this.activeSession.sessionId : ''}`
292
+ );
293
+ await this.loadActiveSessionDetail();
294
+ }
295
+ }
296
+ }
297
+ } catch (e) {
298
+ emitSessionLoadDebug(this, 'loadSessions:exception', e && e.message ? e.message : String(e));
299
+ this.sessionsList = [];
300
+ if (typeof this.primeSessionListRender === 'function') {
301
+ this.primeSessionListRender();
302
+ }
303
+ this.activeSession = null;
304
+ this.activeSessionMessages = [];
305
+ this.resetSessionDetailPagination();
306
+ this.resetSessionPreviewMessageRender();
307
+ this.activeSessionDetailClipped = false;
308
+ this.cancelSessionTimelineSync();
309
+ this.sessionTimelineActiveKey = '';
310
+ clearSessionTimelineRefs(this);
311
+ this.showMessage('加载会话失败', 'error');
312
+ } finally {
313
+ this.sessionsLoading = false;
314
+ if (loadSucceeded) {
315
+ this.sessionsLoadedOnce = true;
316
+ }
317
+ pendingOptions = this.__sessionPendingLoadOptions || null;
318
+ this.__sessionPendingLoadOptions = null;
319
+ emitSessionLoadDebug(
320
+ this,
321
+ 'loadSessions:complete',
322
+ `loadSucceeded=${loadSucceeded}\npendingReload=${!!pendingOptions}`
323
+ );
324
+ }
325
+ if (pendingOptions) {
326
+ emitSessionLoadDebug(this, 'loadSessions:replay-pending-request');
327
+ return loadSessions.call(this, api, pendingOptions);
328
+ }
329
+ }
330
+
331
+ export async function loadActiveSessionDetail(api, options = {}) {
332
+ if (!this.activeSession) {
333
+ emitSessionLoadDebug(this, 'loadActiveSessionDetail:skip-no-active-session');
334
+ this.activeSessionMessages = [];
335
+ this.resetSessionDetailPagination();
336
+ this.resetSessionPreviewMessageRender();
337
+ this.activeSessionDetailError = '';
338
+ this.activeSessionDetailClipped = false;
339
+ this.cancelSessionTimelineSync();
340
+ this.sessionTimelineActiveKey = '';
341
+ clearSessionTimelineRefs(this);
342
+ return;
343
+ }
344
+
345
+ const currentActiveSession = this.activeSession;
346
+ const requestSeq = ++this.sessionDetailRequestSeq;
347
+ this.sessionDetailLoading = true;
348
+ this.activeSessionDetailError = '';
349
+ emitSessionLoadDebug(
350
+ this,
351
+ 'loadActiveSessionDetail:start',
352
+ `sessionId=${currentActiveSession && currentActiveSession.sessionId ? currentActiveSession.sessionId : ''}\nrequestSeq=${requestSeq}`
353
+ );
354
+ const fallbackLimit = Number.isFinite(this.sessionDetailInitialMessageLimit)
355
+ ? Math.max(1, Math.floor(this.sessionDetailInitialMessageLimit))
356
+ : 80;
357
+ const rawLimit = Number(this.sessionDetailMessageLimit);
358
+ const messageLimit = Number.isFinite(rawLimit)
359
+ ? Math.max(1, Math.floor(rawLimit))
360
+ : fallbackLimit;
361
+ const preview = this.mainTab === 'sessions' && !this.sessionStandalone;
362
+ try {
363
+ const res = await api('session-detail', {
364
+ source: this.activeSession.source,
365
+ sessionId: this.activeSession.sessionId,
366
+ filePath: this.activeSession.filePath,
367
+ messageLimit,
368
+ preview
369
+ });
370
+
371
+ if (requestSeq !== this.sessionDetailRequestSeq) {
372
+ emitSessionLoadDebug(this, 'loadActiveSessionDetail:stale-request-seq', `requestSeq=${requestSeq}`);
373
+ return;
374
+ }
375
+ if (!this.activeSession || this.activeSession !== currentActiveSession) {
376
+ emitSessionLoadDebug(this, 'loadActiveSessionDetail:active-session-changed', `requestSeq=${requestSeq}`);
377
+ return;
378
+ }
379
+
380
+ if (res.error) {
381
+ emitSessionLoadDebug(this, 'loadActiveSessionDetail:error-response', `error=${res.error}`);
382
+ this.activeSessionMessages = [];
383
+ this.resetSessionPreviewMessageRender();
384
+ this.activeSessionDetailClipped = false;
385
+ this.activeSessionDetailError = res.error;
386
+ this.cancelSessionTimelineSync();
387
+ this.sessionTimelineActiveKey = '';
388
+ clearSessionTimelineRefs(this);
389
+ return;
390
+ }
391
+
392
+ const rawMessages = Array.isArray(res.messages) ? res.messages : [];
393
+ const normalizedMessages = rawMessages.map((message) => Object.freeze(this.normalizeSessionMessage(message)));
394
+ this.activeSessionMessages = Object.freeze(normalizedMessages);
395
+ emitSessionLoadDebug(
396
+ this,
397
+ 'loadActiveSessionDetail:messages-ready',
398
+ `sessionId=${currentActiveSession && currentActiveSession.sessionId ? currentActiveSession.sessionId : ''}\nmessages=${normalizedMessages.length}\nclipped=${!!res.clipped}`
399
+ );
400
+ if (typeof this.invalidateSessionTimelineMeasurementCache === 'function') {
401
+ this.invalidateSessionTimelineMeasurementCache(true);
402
+ }
403
+ this.activeSessionDetailClipped = !!res.clipped;
404
+ const responseLimitRaw = Number(res.messageLimit);
405
+ this.sessionDetailMessageLimit = Number.isFinite(responseLimitRaw)
406
+ ? Math.max(1, Math.floor(responseLimitRaw))
407
+ : messageLimit;
408
+ if (res.sourceLabel) {
409
+ this.activeSession.sourceLabel = res.sourceLabel;
410
+ }
411
+ if (res.sessionId) {
412
+ this.activeSession.sessionId = res.sessionId;
413
+ if (!this.activeSession.title) {
414
+ this.activeSession.title = res.sessionId;
415
+ }
416
+ }
417
+ if (res.filePath) {
418
+ this.activeSession.filePath = res.filePath;
419
+ }
420
+ if (res.updatedAt) {
421
+ this.activeSession.updatedAt = res.updatedAt;
422
+ }
423
+ if (res.cwd) {
424
+ this.activeSession.cwd = res.cwd;
425
+ }
426
+ if (Number.isFinite(res.totalMessages)) {
427
+ this.syncActiveSessionMessageCount(res.totalMessages);
428
+ }
429
+ if (this.mainTab === 'sessions' && this.sessionPreviewRenderEnabled) {
430
+ const preserveVisibleCount = !!options.preserveVisibleCount;
431
+ const pendingVisibleRaw = Number(this.sessionPreviewPendingVisibleCount);
432
+ const pendingVisible = Number.isFinite(pendingVisibleRaw)
433
+ ? Math.max(0, Math.floor(pendingVisibleRaw))
434
+ : 0;
435
+ if (preserveVisibleCount && pendingVisible > 0) {
436
+ this.sessionPreviewVisibleCount = Math.min(pendingVisible, this.activeSessionMessages.length);
437
+ } else {
438
+ this.primeSessionPreviewMessageRender();
439
+ }
440
+ }
441
+ this.sessionPreviewPendingVisibleCount = 0;
442
+ this.$nextTick(() => {
443
+ if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
444
+ return;
445
+ }
446
+ this.updateSessionTimelineOffset();
447
+ if (this.sessionTimelineEnabled) {
448
+ const currentSession = this.activeSession;
449
+ const syncTask = () => {
450
+ if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
451
+ return;
452
+ }
453
+ if (!this.activeSession || this.activeSession !== currentSession) {
454
+ return;
455
+ }
456
+ this.scheduleSessionTimelineSync();
457
+ };
458
+ if (typeof this.scheduleAfterFrame === 'function') {
459
+ this.scheduleAfterFrame(syncTask);
460
+ return;
461
+ }
462
+ syncTask();
463
+ }
464
+ });
465
+ } catch (e) {
466
+ if (requestSeq !== this.sessionDetailRequestSeq) {
467
+ emitSessionLoadDebug(this, 'loadActiveSessionDetail:ignore-exception-stale-request', `requestSeq=${requestSeq}`);
468
+ return;
469
+ }
470
+ if (!this.activeSession || this.activeSession !== currentActiveSession) {
471
+ emitSessionLoadDebug(this, 'loadActiveSessionDetail:ignore-exception-session-changed', `requestSeq=${requestSeq}`);
472
+ return;
473
+ }
474
+ emitSessionLoadDebug(this, 'loadActiveSessionDetail:exception', e && e.message ? e.message : String(e));
475
+ this.activeSessionMessages = [];
476
+ this.sessionPreviewPendingVisibleCount = 0;
477
+ this.resetSessionPreviewMessageRender();
478
+ this.activeSessionDetailClipped = false;
479
+ this.activeSessionDetailError = '加载会话内容失败: ' + e.message;
480
+ this.cancelSessionTimelineSync();
481
+ this.sessionTimelineActiveKey = '';
482
+ clearSessionTimelineRefs(this);
483
+ } finally {
484
+ if (requestSeq === this.sessionDetailRequestSeq) {
485
+ this.sessionDetailLoading = false;
486
+ }
487
+ emitSessionLoadDebug(this, 'loadActiveSessionDetail:complete', `requestSeq=${requestSeq}`);
488
+ }
489
+ }
490
+
491
+ export async function loadMoreSessionMessages(stepSize) {
492
+ if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
493
+ return;
494
+ }
495
+ const total = Array.isArray(this.activeSessionMessages)
496
+ ? this.activeSessionMessages.length
497
+ : 0;
498
+ if (total <= 0) {
499
+ this.sessionPreviewVisibleCount = 0;
500
+ return;
501
+ }
502
+ const step = Number.isFinite(stepSize)
503
+ ? Math.max(1, Math.floor(stepSize))
504
+ : (Number.isFinite(this.sessionPreviewLoadStep)
505
+ ? Math.max(1, Math.floor(this.sessionPreviewLoadStep))
506
+ : 40);
507
+ const current = Number.isFinite(this.sessionPreviewVisibleCount)
508
+ ? Math.max(0, Math.floor(this.sessionPreviewVisibleCount))
509
+ : 0;
510
+ const targetVisible = current + step;
511
+ if (targetVisible <= total) {
512
+ this.sessionPreviewVisibleCount = Math.min(total, targetVisible);
513
+ return;
514
+ }
515
+
516
+ this.sessionPreviewVisibleCount = total;
517
+ if (this.sessionDetailLoading) {
518
+ return;
519
+ }
520
+
521
+ const totalKnownRaw = Number(this.activeSession && this.activeSession.messageCount);
522
+ const totalKnown = Number.isFinite(totalKnownRaw)
523
+ ? Math.max(0, Math.floor(totalKnownRaw))
524
+ : 0;
525
+ const hasMoreOnDisk = this.activeSessionDetailClipped || (totalKnown > total);
526
+ if (!hasMoreOnDisk) {
527
+ return;
528
+ }
529
+
530
+ const currentLimitRaw = Number(this.sessionDetailMessageLimit);
531
+ const currentLimit = Number.isFinite(currentLimitRaw)
532
+ ? Math.max(1, Math.floor(currentLimitRaw))
533
+ : Math.max(1, total);
534
+ const fetchStep = Number.isFinite(this.sessionDetailFetchStep)
535
+ ? Math.max(1, Math.floor(this.sessionDetailFetchStep))
536
+ : 80;
537
+ const limitCapRaw = Number(this.sessionDetailMessageLimitCap);
538
+ const limitCap = Number.isFinite(limitCapRaw)
539
+ ? Math.max(1, Math.floor(limitCapRaw))
540
+ : 1000;
541
+
542
+ let nextLimit = Math.max(currentLimit + fetchStep, targetVisible);
543
+ if (totalKnown > total) {
544
+ nextLimit = Math.min(nextLimit, totalKnown);
545
+ }
546
+ nextLimit = Math.min(nextLimit, limitCap);
547
+ if (nextLimit <= currentLimit) {
548
+ return;
549
+ }
550
+
551
+ this.sessionPreviewPendingVisibleCount = targetVisible;
552
+ this.sessionDetailMessageLimit = nextLimit;
553
+ this.sessionPreviewLoadingMore = true;
554
+ try {
555
+ await this.loadActiveSessionDetail({ preserveVisibleCount: true });
556
+ } finally {
557
+ this.sessionPreviewLoadingMore = false;
558
+ }
559
+ }