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.
- package/README.md +289 -152
- package/README.zh.md +321 -0
- package/cli/agents-files.js +224 -0
- package/cli/archive-helpers.js +446 -0
- package/cli/auth-profiles.js +359 -0
- package/cli/builtin-proxy.js +1044 -0
- package/cli/claude-proxy.js +998 -0
- package/cli/config-bootstrap.js +384 -0
- package/cli/openai-bridge.js +950 -0
- package/cli/openclaw-config.js +629 -0
- package/cli/session-usage.concurrent.js +28 -0
- package/cli/session-usage.js +112 -0
- package/cli/session-usage.models.js +176 -0
- package/cli/skills.js +1141 -0
- package/cli/zip-commands.js +510 -0
- package/cli.js +9408 -9719
- package/lib/cli-models-utils.js +109 -1
- package/lib/cli-path-utils.js +69 -0
- package/lib/cli-sessions.js +386 -0
- package/lib/download-artifacts.js +77 -0
- package/lib/task-orchestrator.js +869 -0
- package/package.json +14 -10
- package/res/logo.png +0 -0
- package/res/vue.global.prod.js +13 -0
- package/web-ui/app.js +193 -15
- package/web-ui/index.html +5 -1
- package/web-ui/logic.agents-diff.mjs +1 -1
- package/web-ui/logic.claude.mjs +60 -0
- package/web-ui/logic.runtime.mjs +11 -7
- package/web-ui/logic.sessions.mjs +372 -21
- package/web-ui/modules/api.mjs +22 -1
- package/web-ui/modules/app.computed.dashboard.mjs +23 -10
- package/web-ui/modules/app.computed.index.mjs +4 -0
- package/web-ui/modules/app.computed.main-tabs.mjs +198 -0
- package/web-ui/modules/app.computed.session.mjs +521 -9
- package/web-ui/modules/app.methods.agents.mjs +62 -11
- package/web-ui/modules/app.methods.codex-config.mjs +189 -34
- package/web-ui/modules/app.methods.index.mjs +7 -1
- package/web-ui/modules/app.methods.install.mjs +24 -20
- package/web-ui/modules/app.methods.navigation.mjs +142 -1
- package/web-ui/modules/app.methods.openclaw-core.mjs +339 -39
- package/web-ui/modules/app.methods.openclaw-editing.mjs +39 -4
- package/web-ui/modules/app.methods.openclaw-persist.mjs +122 -4
- package/web-ui/modules/app.methods.providers.mjs +192 -53
- package/web-ui/modules/app.methods.session-actions.mjs +99 -19
- package/web-ui/modules/app.methods.session-browser.mjs +196 -5
- package/web-ui/modules/app.methods.session-timeline.mjs +22 -15
- package/web-ui/modules/app.methods.session-trash.mjs +3 -0
- package/web-ui/modules/app.methods.startup-claude.mjs +70 -71
- package/web-ui/modules/app.methods.task-orchestration.mjs +471 -0
- package/web-ui/modules/config-mode.computed.mjs +2 -0
- package/web-ui/modules/config-template-confirm-pref.mjs +33 -0
- package/web-ui/modules/i18n.mjs +1609 -0
- package/web-ui/modules/plugins.computed.mjs +220 -0
- package/web-ui/modules/plugins.methods.mjs +620 -0
- package/web-ui/modules/plugins.storage.mjs +37 -0
- package/web-ui/partials/index/layout-footer.html +1 -57
- package/web-ui/partials/index/layout-header.html +299 -175
- package/web-ui/partials/index/modal-config-template-agents.html +79 -29
- package/web-ui/partials/index/modal-confirm-toast.html +1 -1
- package/web-ui/partials/index/modal-health-check.html +14 -14
- package/web-ui/partials/index/modal-openclaw-config.html +47 -42
- package/web-ui/partials/index/modal-skills.html +130 -114
- package/web-ui/partials/index/modals-basic.html +71 -102
- package/web-ui/partials/index/panel-config-claude.html +50 -12
- package/web-ui/partials/index/panel-config-codex.html +34 -37
- package/web-ui/partials/index/panel-config-openclaw.html +10 -16
- package/web-ui/partials/index/panel-docs.html +147 -0
- package/web-ui/partials/index/panel-market.html +38 -38
- package/web-ui/partials/index/panel-orchestration.html +397 -0
- package/web-ui/partials/index/panel-plugins.html +243 -0
- package/web-ui/partials/index/panel-sessions.html +51 -146
- package/web-ui/partials/index/panel-settings.html +188 -96
- package/web-ui/partials/index/panel-usage.html +353 -0
- package/web-ui/session-helpers.mjs +221 -10
- package/web-ui/styles/base-theme.css +120 -229
- package/web-ui/styles/controls-forms.css +59 -51
- package/web-ui/styles/docs-panel.css +247 -0
- package/web-ui/styles/layout-shell.css +394 -128
- package/web-ui/styles/modals-core.css +18 -3
- package/web-ui/styles/navigation-panels.css +184 -183
- package/web-ui/styles/plugins-panel.css +518 -0
- package/web-ui/styles/responsive.css +102 -62
- package/web-ui/styles/sessions-list.css +13 -27
- package/web-ui/styles/sessions-preview.css +13 -7
- package/web-ui/styles/sessions-toolbar-trash.css +25 -0
- package/web-ui/styles/sessions-usage.css +581 -6
- package/web-ui/styles/settings-panel.css +166 -0
- package/web-ui/styles/skills-list.css +16 -11
- package/web-ui/styles/skills-market.css +63 -2
- package/web-ui/styles/task-orchestration.css +776 -0
- package/web-ui/styles/titles-cards.css +67 -66
- package/web-ui/styles.css +4 -0
- package/README.en.md +0 -259
- package/res/screenshot.png +0 -0
- package/res/vue.global.js +0 -18552
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
export const DEFAULT_SESSION_LIST_LIMIT = 200;
|
|
2
|
+
export const DEFAULT_SESSION_LIST_FAST_LIMIT = 20;
|
|
3
|
+
|
|
4
|
+
function shouldUseFastSessionBrowseLimit(options = {}) {
|
|
5
|
+
if (options.forceRefresh) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
const normalizedSource = normalizeSessionSource(options.source, 'all');
|
|
9
|
+
if (normalizedSource !== 'all') {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
const pathFilter = normalizeSessionPathFilter(options.pathFilter);
|
|
13
|
+
if (pathFilter) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const query = typeof options.query === 'string' ? options.query.trim() : '';
|
|
17
|
+
if (query) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const roleFilter = typeof options.roleFilter === 'string' ? options.roleFilter.trim().toLowerCase() : 'all';
|
|
21
|
+
if (roleFilter && roleFilter !== 'all') {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const timeRangePreset = typeof options.timeRangePreset === 'string'
|
|
25
|
+
? options.timeRangePreset.trim().toLowerCase()
|
|
26
|
+
: 'all';
|
|
27
|
+
return !timeRangePreset || timeRangePreset === 'all';
|
|
28
|
+
}
|
|
29
|
+
|
|
1
30
|
export function isSessionQueryEnabled(source) {
|
|
2
31
|
const normalized = normalizeSessionSource(source, '');
|
|
3
32
|
return normalized === 'codex' || normalized === 'claude' || normalized === 'all';
|
|
@@ -17,6 +46,73 @@ export function normalizeSessionPathFilter(pathFilter) {
|
|
|
17
46
|
return typeof pathFilter === 'string' ? pathFilter.trim() : '';
|
|
18
47
|
}
|
|
19
48
|
|
|
49
|
+
function isConcreteSessionModelName(value) {
|
|
50
|
+
if (typeof value !== 'string') {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
const normalized = value.trim();
|
|
54
|
+
if (!normalized) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return normalized.toLowerCase() !== '<synthetic>';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function collectSessionModelNames(session) {
|
|
61
|
+
if (!session || typeof session !== 'object') {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
const values = Array.isArray(session.models)
|
|
65
|
+
? [...session.models, session.model, session.modelName, session.modelId]
|
|
66
|
+
: [session.model, session.modelName, session.modelId];
|
|
67
|
+
const models = [];
|
|
68
|
+
for (const value of values) {
|
|
69
|
+
if (!isConcreteSessionModelName(value)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const normalized = value.trim();
|
|
73
|
+
if (models.includes(normalized)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
models.push(normalized);
|
|
77
|
+
}
|
|
78
|
+
return models;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function readSessionTotalTokens(session) {
|
|
82
|
+
if (!session || typeof session !== 'object') {
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const rawTotalTokens = Number(session.totalTokens);
|
|
87
|
+
const hasExplicitTotal = Number.isFinite(rawTotalTokens) && rawTotalTokens >= 0;
|
|
88
|
+
const explicitTotal = hasExplicitTotal ? Math.max(0, Math.floor(rawTotalTokens)) : null;
|
|
89
|
+
|
|
90
|
+
const inputTokens = Number.isFinite(Number(session.inputTokens))
|
|
91
|
+
? Math.max(0, Math.floor(Number(session.inputTokens)))
|
|
92
|
+
: null;
|
|
93
|
+
const outputTokens = Number.isFinite(Number(session.outputTokens))
|
|
94
|
+
? Math.max(0, Math.floor(Number(session.outputTokens)))
|
|
95
|
+
: null;
|
|
96
|
+
const reasoningOutputTokens = Number.isFinite(Number(session.reasoningOutputTokens))
|
|
97
|
+
? Math.max(0, Math.floor(Number(session.reasoningOutputTokens)))
|
|
98
|
+
: 0;
|
|
99
|
+
|
|
100
|
+
// 对齐 usage 口径:当总 token 缺失时,使用拆分字段回填(input + output + reasoning)。
|
|
101
|
+
// cachedInputTokens 一般包含在 inputTokens 中,因此不在此重复相加。
|
|
102
|
+
const hasBreakdown = !(inputTokens === null && outputTokens === null && reasoningOutputTokens === 0);
|
|
103
|
+
const breakdownTotal = hasBreakdown
|
|
104
|
+
? (inputTokens || 0) + (outputTokens || 0) + reasoningOutputTokens
|
|
105
|
+
: 0;
|
|
106
|
+
|
|
107
|
+
if (breakdownTotal > 0) {
|
|
108
|
+
return breakdownTotal;
|
|
109
|
+
}
|
|
110
|
+
if (explicitTotal !== null) {
|
|
111
|
+
return explicitTotal;
|
|
112
|
+
}
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
20
116
|
export function buildSessionFilterCacheState(source, pathFilter) {
|
|
21
117
|
return {
|
|
22
118
|
source: normalizeSessionSource(source, 'all'),
|
|
@@ -25,13 +121,17 @@ export function buildSessionFilterCacheState(source, pathFilter) {
|
|
|
25
121
|
}
|
|
26
122
|
|
|
27
123
|
export function buildSessionListParams(options = {}) {
|
|
124
|
+
const fallbackLimit = shouldUseFastSessionBrowseLimit(options)
|
|
125
|
+
? DEFAULT_SESSION_LIST_FAST_LIMIT
|
|
126
|
+
: DEFAULT_SESSION_LIST_LIMIT;
|
|
28
127
|
const {
|
|
29
128
|
source = 'all',
|
|
30
129
|
pathFilter = '',
|
|
31
130
|
query = '',
|
|
32
131
|
roleFilter = 'all',
|
|
33
132
|
timeRangePreset = 'all',
|
|
34
|
-
limit =
|
|
133
|
+
limit = fallbackLimit,
|
|
134
|
+
forceRefresh = false
|
|
35
135
|
} = options;
|
|
36
136
|
const normalizedSource = normalizeSessionSource(source, 'all');
|
|
37
137
|
const normalizedPathFilter = normalizeSessionPathFilter(pathFilter);
|
|
@@ -46,7 +146,7 @@ export function buildSessionListParams(options = {}) {
|
|
|
46
146
|
roleFilter,
|
|
47
147
|
timeRangePreset,
|
|
48
148
|
limit,
|
|
49
|
-
forceRefresh:
|
|
149
|
+
forceRefresh: !!forceRefresh
|
|
50
150
|
};
|
|
51
151
|
}
|
|
52
152
|
|
|
@@ -88,16 +188,54 @@ export function formatSessionTimelineTimestamp(timestamp) {
|
|
|
88
188
|
return value;
|
|
89
189
|
}
|
|
90
190
|
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
191
|
+
function normalizeUsageRange(range) {
|
|
192
|
+
const normalized = typeof range === 'string' ? range.trim().toLowerCase() : '7d';
|
|
193
|
+
if (normalized === '30d' || normalized === 'all') {
|
|
194
|
+
return normalized;
|
|
195
|
+
}
|
|
196
|
+
return '7d';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function toUtcDayStartMs(value) {
|
|
200
|
+
const stamp = new Date(value);
|
|
201
|
+
return Date.UTC(stamp.getUTCFullYear(), stamp.getUTCMonth(), stamp.getUTCDate());
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function formatUtcDayKey(value) {
|
|
205
|
+
const stamp = new Date(value);
|
|
206
|
+
return `${stamp.getUTCFullYear()}-${String(stamp.getUTCMonth() + 1).padStart(2, '0')}-${String(stamp.getUTCDate()).padStart(2, '0')}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function buildUsageBuckets(normalizedSessions, options = {}) {
|
|
210
|
+
const range = normalizeUsageRange(options.range);
|
|
94
211
|
const now = Number.isFinite(Number(options.now)) ? Number(options.now) : Date.now();
|
|
95
212
|
const dayMs = 24 * 60 * 60 * 1000;
|
|
96
|
-
const rangeDays = range === '30d' ? 30 : 7;
|
|
97
213
|
const buckets = [];
|
|
214
|
+
|
|
215
|
+
if (range === 'all') {
|
|
216
|
+
const validDayStarts = normalizedSessions
|
|
217
|
+
.map((session) => toUtcDayStartMs(session.updatedAtMs))
|
|
218
|
+
.filter((value) => Number.isFinite(value));
|
|
219
|
+
const firstDayStart = validDayStarts.length ? Math.min(...validDayStarts) : toUtcDayStartMs(now);
|
|
220
|
+
const lastDayStart = validDayStarts.length ? Math.max(...validDayStarts) : toUtcDayStartMs(now);
|
|
221
|
+
for (let stamp = firstDayStart; stamp <= lastDayStart; stamp += dayMs) {
|
|
222
|
+
const key = formatUtcDayKey(stamp);
|
|
223
|
+
buckets.push({
|
|
224
|
+
key,
|
|
225
|
+
label: key.slice(5),
|
|
226
|
+
codex: 0,
|
|
227
|
+
claude: 0,
|
|
228
|
+
totalMessages: 0,
|
|
229
|
+
totalSessions: 0
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return { range, buckets };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const rangeDays = range === '30d' ? 30 : 7;
|
|
98
236
|
for (let i = rangeDays - 1; i >= 0; i -= 1) {
|
|
99
237
|
const stamp = new Date(now - (i * dayMs));
|
|
100
|
-
const key =
|
|
238
|
+
const key = formatUtcDayKey(stamp);
|
|
101
239
|
buckets.push({
|
|
102
240
|
key,
|
|
103
241
|
label: key.slice(5),
|
|
@@ -107,25 +245,80 @@ export function buildUsageChartGroups(sessions = [], options = {}) {
|
|
|
107
245
|
totalSessions: 0
|
|
108
246
|
});
|
|
109
247
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
let claudeTotal = 0;
|
|
113
|
-
let messageTotal = 0;
|
|
114
|
-
const pathMap = new Map();
|
|
248
|
+
return { range, buckets };
|
|
249
|
+
}
|
|
115
250
|
|
|
116
|
-
|
|
251
|
+
export function buildUsageChartGroups(sessions = [], options = {}) {
|
|
252
|
+
const list = Array.isArray(sessions) ? sessions : [];
|
|
253
|
+
const normalizedSessions = [];
|
|
254
|
+
for (const [sessionIndex, session] of list.entries()) {
|
|
117
255
|
if (!session || typeof session !== 'object') continue;
|
|
118
256
|
const source = normalizeSessionSource(session.source, '');
|
|
119
257
|
if (source !== 'codex' && source !== 'claude') continue;
|
|
120
258
|
const updatedAtMs = Date.parse(session.updatedAt || '');
|
|
121
259
|
if (!Number.isFinite(updatedAtMs)) continue;
|
|
260
|
+
const createdAtMs = Date.parse(session.createdAt || '');
|
|
261
|
+
const sessionStartedAtMs = Number.isFinite(createdAtMs) ? createdAtMs : updatedAtMs;
|
|
262
|
+
const sessionEndedAtMs = Math.max(updatedAtMs, sessionStartedAtMs);
|
|
263
|
+
normalizedSessions.push({
|
|
264
|
+
session,
|
|
265
|
+
sessionIndex,
|
|
266
|
+
source,
|
|
267
|
+
updatedAtMs,
|
|
268
|
+
createdAtMs,
|
|
269
|
+
sessionStartedAtMs,
|
|
270
|
+
sessionEndedAtMs,
|
|
271
|
+
bucketKey: formatUtcDayKey(updatedAtMs)
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
const { range, buckets } = buildUsageBuckets(normalizedSessions, options);
|
|
275
|
+
const bucketMap = new Map(buckets.map((bucket) => [bucket.key, bucket]));
|
|
276
|
+
let codexTotal = 0;
|
|
277
|
+
let claudeTotal = 0;
|
|
278
|
+
let messageTotal = 0;
|
|
279
|
+
let totalTokens = 0;
|
|
280
|
+
let totalContextWindow = 0;
|
|
281
|
+
let activeDurationMs = 0;
|
|
282
|
+
let earliestSessionMs = Number.POSITIVE_INFINITY;
|
|
283
|
+
let latestSessionMs = 0;
|
|
284
|
+
const pathMap = new Map();
|
|
285
|
+
const modelMap = new Map();
|
|
286
|
+
const missingModelProviderMap = new Map();
|
|
287
|
+
const missingModelSessionMap = new Map();
|
|
288
|
+
const sourceMessageTotals = { codex: 0, claude: 0 };
|
|
289
|
+
const missingModelSourceTotals = { codex: 0, claude: 0 };
|
|
290
|
+
let missingModelSessions = 0;
|
|
291
|
+
let providerOnlySessions = 0;
|
|
292
|
+
const hourCounts = Array.from({ length: 24 }, (_, hour) => ({
|
|
293
|
+
key: String(hour).padStart(2, '0'),
|
|
294
|
+
label: String(hour).padStart(2, '0'),
|
|
295
|
+
count: 0
|
|
296
|
+
}));
|
|
297
|
+
const weekdayLabels = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
|
|
298
|
+
const weekdayCounts = Array.from({ length: 7 }, (_, index) => ({
|
|
299
|
+
key: String(index),
|
|
300
|
+
label: weekdayLabels[index],
|
|
301
|
+
count: 0
|
|
302
|
+
}));
|
|
303
|
+
const recentSessions = [];
|
|
304
|
+
const topSessionsByMessages = [];
|
|
305
|
+
const filteredSessions = [];
|
|
306
|
+
|
|
307
|
+
for (const normalized of normalizedSessions) {
|
|
308
|
+
const { session, sessionIndex, source, updatedAtMs, sessionStartedAtMs, sessionEndedAtMs, bucketKey } = normalized;
|
|
122
309
|
const stamp = new Date(updatedAtMs);
|
|
123
|
-
const
|
|
124
|
-
const bucket = bucketMap.get(key);
|
|
310
|
+
const bucket = bucketMap.get(bucketKey);
|
|
125
311
|
if (!bucket) continue;
|
|
312
|
+
const sessionModels = collectSessionModelNames(session);
|
|
313
|
+
if (sessionModels.length === 0) continue;
|
|
314
|
+
filteredSessions.push(session);
|
|
126
315
|
const messageCount = Number.isFinite(Number(session.messageCount))
|
|
127
316
|
? Math.max(0, Math.floor(Number(session.messageCount)))
|
|
128
317
|
: 0;
|
|
318
|
+
const sessionTotalTokens = readSessionTotalTokens(session);
|
|
319
|
+
const sessionContextWindow = Number.isFinite(Number(session.contextWindow))
|
|
320
|
+
? Math.max(0, Math.floor(Number(session.contextWindow)))
|
|
321
|
+
: 0;
|
|
129
322
|
bucket.totalSessions += 1;
|
|
130
323
|
bucket.totalMessages += messageCount;
|
|
131
324
|
if (source === 'codex') {
|
|
@@ -136,10 +329,71 @@ export function buildUsageChartGroups(sessions = [], options = {}) {
|
|
|
136
329
|
claudeTotal += 1;
|
|
137
330
|
}
|
|
138
331
|
messageTotal += messageCount;
|
|
332
|
+
totalTokens += sessionTotalTokens;
|
|
333
|
+
totalContextWindow += sessionContextWindow;
|
|
334
|
+
sourceMessageTotals[source] += messageCount;
|
|
335
|
+
activeDurationMs += Math.max(0, sessionEndedAtMs - sessionStartedAtMs);
|
|
336
|
+
earliestSessionMs = Math.min(earliestSessionMs, sessionStartedAtMs);
|
|
337
|
+
latestSessionMs = Math.max(latestSessionMs, sessionEndedAtMs);
|
|
338
|
+
|
|
339
|
+
const utcHour = stamp.getUTCHours();
|
|
340
|
+
if (hourCounts[utcHour]) {
|
|
341
|
+
hourCounts[utcHour].count += 1;
|
|
342
|
+
}
|
|
343
|
+
const dayIndex = (stamp.getUTCDay() + 6) % 7;
|
|
344
|
+
if (weekdayCounts[dayIndex]) {
|
|
345
|
+
weekdayCounts[dayIndex].count += 1;
|
|
346
|
+
}
|
|
347
|
+
|
|
139
348
|
const cwd = normalizeSessionPathFilter(session.cwd);
|
|
140
349
|
if (cwd) {
|
|
141
|
-
|
|
350
|
+
const prev = pathMap.get(cwd) || { count: 0, messageTotal: 0, updatedAtMs: 0 };
|
|
351
|
+
pathMap.set(cwd, {
|
|
352
|
+
count: prev.count + 1,
|
|
353
|
+
messageTotal: prev.messageTotal + messageCount,
|
|
354
|
+
updatedAtMs: Math.max(prev.updatedAtMs, updatedAtMs)
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const sourceLabel = source === 'codex' ? 'Codex' : 'Claude Code';
|
|
359
|
+
const normalizedTitle = typeof session.title === 'string' && session.title.trim()
|
|
360
|
+
? session.title.trim()
|
|
361
|
+
: (typeof session.sessionId === 'string' && session.sessionId.trim() ? session.sessionId.trim() : '未命名会话');
|
|
362
|
+
for (const modelId of sessionModels) {
|
|
363
|
+
const prev = modelMap.get(modelId) || {
|
|
364
|
+
count: 0,
|
|
365
|
+
messageTotal: 0,
|
|
366
|
+
tokenTotal: 0,
|
|
367
|
+
sources: new Set()
|
|
368
|
+
};
|
|
369
|
+
prev.count += 1;
|
|
370
|
+
prev.messageTotal += messageCount;
|
|
371
|
+
prev.tokenTotal += sessionTotalTokens;
|
|
372
|
+
prev.sources.add(source);
|
|
373
|
+
modelMap.set(modelId, prev);
|
|
142
374
|
}
|
|
375
|
+
|
|
376
|
+
const sessionEntry = {
|
|
377
|
+
key: [
|
|
378
|
+
source,
|
|
379
|
+
session.sessionId || '',
|
|
380
|
+
session.filePath || normalizedTitle,
|
|
381
|
+
String(updatedAtMs),
|
|
382
|
+
String(messageCount),
|
|
383
|
+
String(sessionIndex)
|
|
384
|
+
].join(':'),
|
|
385
|
+
title: normalizedTitle,
|
|
386
|
+
source,
|
|
387
|
+
sourceLabel,
|
|
388
|
+
cwd,
|
|
389
|
+
messageCount,
|
|
390
|
+
updatedAt: session.updatedAt || '',
|
|
391
|
+
updatedAtMs,
|
|
392
|
+
updatedAtLabel: formatSessionTimelineTimestamp(session.updatedAt || ''),
|
|
393
|
+
hasExactMessageCount: session.__messageCountExact === true
|
|
394
|
+
};
|
|
395
|
+
recentSessions.push(sessionEntry);
|
|
396
|
+
topSessionsByMessages.push({ ...sessionEntry });
|
|
143
397
|
}
|
|
144
398
|
|
|
145
399
|
const totalSessions = codexTotal + claudeTotal;
|
|
@@ -148,31 +402,128 @@ export function buildUsageChartGroups(sessions = [], options = {}) {
|
|
|
148
402
|
{ key: 'claude', label: 'Claude', value: claudeTotal }
|
|
149
403
|
].map((item) => ({
|
|
150
404
|
...item,
|
|
151
|
-
percent: totalSessions > 0 ? Math.round((item.value / totalSessions) * 100) : 0
|
|
405
|
+
percent: totalSessions > 0 ? Math.round((item.value / totalSessions) * 100) : 0,
|
|
406
|
+
messageTotal: sourceMessageTotals[item.key] || 0,
|
|
407
|
+
messagePercent: messageTotal > 0 ? Math.round(((sourceMessageTotals[item.key] || 0) / messageTotal) * 100) : 0,
|
|
408
|
+
avgMessages: item.value > 0 ? Math.round(((sourceMessageTotals[item.key] || 0) / item.value) * 10) / 10 : 0
|
|
152
409
|
}));
|
|
153
410
|
|
|
154
411
|
const topPaths = [...pathMap.entries()]
|
|
155
|
-
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0], 'zh-Hans-CN'))
|
|
412
|
+
.sort((a, b) => b[1].count - a[1].count || b[1].messageTotal - a[1].messageTotal || a[0].localeCompare(b[0], 'zh-Hans-CN'))
|
|
156
413
|
.slice(0, 5)
|
|
157
|
-
.map(([pathValue,
|
|
414
|
+
.map(([pathValue, meta]) => ({
|
|
415
|
+
path: pathValue,
|
|
416
|
+
count: meta.count,
|
|
417
|
+
messageTotal: meta.messageTotal,
|
|
418
|
+
updatedAtLabel: meta.updatedAtMs ? formatSessionTimelineTimestamp(new Date(meta.updatedAtMs).toISOString()) : ''
|
|
419
|
+
}));
|
|
420
|
+
|
|
421
|
+
const usedModels = [...modelMap.entries()]
|
|
422
|
+
.sort((a, b) => b[1].count - a[1].count)
|
|
423
|
+
.map(([modelId, meta]) => {
|
|
424
|
+
const sourceLabels = [...meta.sources]
|
|
425
|
+
.sort((a, b) => a.localeCompare(b, 'en-US'))
|
|
426
|
+
.map((source) => (source === 'codex' ? 'Codex' : 'Claude Code'));
|
|
427
|
+
return {
|
|
428
|
+
key: modelId,
|
|
429
|
+
model: modelId,
|
|
430
|
+
count: meta.count,
|
|
431
|
+
messageTotal: meta.messageTotal,
|
|
432
|
+
tokenTotal: meta.tokenTotal,
|
|
433
|
+
sourceLabels
|
|
434
|
+
};
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const sortedRecentSessions = recentSessions
|
|
438
|
+
.sort((a, b) => b.updatedAtMs - a.updatedAtMs || b.messageCount - a.messageCount || a.title.localeCompare(b.title, 'zh-Hans-CN'))
|
|
439
|
+
.slice(0, 6);
|
|
440
|
+
|
|
441
|
+
const missingModelProviders = [...missingModelProviderMap.values()]
|
|
442
|
+
.sort((a, b) => b.count - a.count || a.label.localeCompare(b.label, 'zh-Hans-CN'));
|
|
443
|
+
const missingModelSessionsPreview = [...missingModelSessionMap.values()]
|
|
444
|
+
.sort((a, b) => b.updatedAtMs - a.updatedAtMs || a.title.localeCompare(b.title, 'zh-Hans-CN'))
|
|
445
|
+
.slice(0, 5);
|
|
446
|
+
|
|
447
|
+
const modelCoverage = {
|
|
448
|
+
totalSessions,
|
|
449
|
+
modeledSessions: Math.max(0, totalSessions - missingModelSessions),
|
|
450
|
+
missingModelSessions,
|
|
451
|
+
providerOnlySessions,
|
|
452
|
+
missingModelSourceTotals,
|
|
453
|
+
missingModelProviders,
|
|
454
|
+
missingModelSessionsPreview,
|
|
455
|
+
coveragePercent: totalSessions > 0 ? Math.round(((totalSessions - missingModelSessions) / totalSessions) * 100) : 0
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
const sortedTopSessionsByMessages = topSessionsByMessages
|
|
459
|
+
.sort((a, b) => b.messageCount - a.messageCount || b.updatedAtMs - a.updatedAtMs || a.title.localeCompare(b.title, 'zh-Hans-CN'))
|
|
460
|
+
.slice(0, 6);
|
|
158
461
|
|
|
159
462
|
const maxSessionBucket = buckets.reduce((max, item) => Math.max(max, item.totalSessions), 0);
|
|
160
463
|
const maxMessageBucket = buckets.reduce((max, item) => Math.max(max, item.totalMessages), 0);
|
|
464
|
+
const maxHourCount = hourCounts.reduce((max, item) => Math.max(max, item.count), 0);
|
|
465
|
+
const maxWeekdayCount = weekdayCounts.reduce((max, item) => Math.max(max, item.count), 0);
|
|
466
|
+
const busiestDay = [...buckets]
|
|
467
|
+
.sort((a, b) => b.totalSessions - a.totalSessions || b.totalMessages - a.totalMessages || a.key.localeCompare(b.key, 'zh-Hans-CN'))[0] || null;
|
|
468
|
+
const busiestHour = [...hourCounts]
|
|
469
|
+
.sort((a, b) => b.count - a.count || a.key.localeCompare(b.key, 'zh-Hans-CN'))[0] || null;
|
|
470
|
+
const activeDays = buckets.filter((item) => item.totalSessions > 0).length;
|
|
471
|
+
const avgMessagesPerSession = totalSessions > 0 ? Math.round((messageTotal / totalSessions) * 10) / 10 : 0;
|
|
472
|
+
const avgSessionsPerActiveDay = activeDays > 0 ? Math.round((totalSessions / activeDays) * 10) / 10 : 0;
|
|
473
|
+
const totalDurationMs = Number.isFinite(earliestSessionMs) && latestSessionMs > 0
|
|
474
|
+
? Math.max(0, latestSessionMs - earliestSessionMs)
|
|
475
|
+
: 0;
|
|
161
476
|
|
|
162
477
|
return {
|
|
163
478
|
range,
|
|
164
479
|
buckets,
|
|
480
|
+
filteredSessions,
|
|
165
481
|
summary: {
|
|
166
482
|
totalSessions,
|
|
167
483
|
totalMessages: messageTotal,
|
|
484
|
+
totalTokens,
|
|
485
|
+
totalContextWindow,
|
|
486
|
+
activeDurationMs,
|
|
487
|
+
totalDurationMs,
|
|
168
488
|
codexTotal,
|
|
169
489
|
claudeTotal,
|
|
170
|
-
activeDays
|
|
490
|
+
activeDays,
|
|
491
|
+
avgMessagesPerSession,
|
|
492
|
+
avgSessionsPerActiveDay,
|
|
493
|
+
busiestDay: busiestDay
|
|
494
|
+
? {
|
|
495
|
+
key: busiestDay.key,
|
|
496
|
+
label: busiestDay.label,
|
|
497
|
+
totalSessions: busiestDay.totalSessions,
|
|
498
|
+
totalMessages: busiestDay.totalMessages
|
|
499
|
+
}
|
|
500
|
+
: null,
|
|
501
|
+
busiestHour: busiestHour
|
|
502
|
+
? {
|
|
503
|
+
key: busiestHour.key,
|
|
504
|
+
label: `${busiestHour.label}:00`,
|
|
505
|
+
count: busiestHour.count
|
|
506
|
+
}
|
|
507
|
+
: null
|
|
171
508
|
},
|
|
172
509
|
sourceShare,
|
|
510
|
+
usedModels,
|
|
511
|
+
modelCoverage,
|
|
173
512
|
topPaths,
|
|
513
|
+
recentSessions: sortedRecentSessions,
|
|
514
|
+
topSessionsByMessages: sortedTopSessionsByMessages,
|
|
515
|
+
hourActivity: hourCounts.map((item) => ({
|
|
516
|
+
...item,
|
|
517
|
+
percent: maxHourCount > 0 ? Math.round((item.count / maxHourCount) * 100) : 0
|
|
518
|
+
})),
|
|
519
|
+
weekdayActivity: weekdayCounts.map((item) => ({
|
|
520
|
+
...item,
|
|
521
|
+
percent: maxWeekdayCount > 0 ? Math.round((item.count / maxWeekdayCount) * 100) : 0
|
|
522
|
+
})),
|
|
174
523
|
maxSessionBucket,
|
|
175
|
-
maxMessageBucket
|
|
524
|
+
maxMessageBucket,
|
|
525
|
+
maxHourCount,
|
|
526
|
+
maxWeekdayCount
|
|
176
527
|
};
|
|
177
528
|
}
|
|
178
529
|
|
package/web-ui/modules/api.mjs
CHANGED
|
@@ -16,6 +16,26 @@ function buildApiResponseContext(action, res, contentType) {
|
|
|
16
16
|
return `${action} (${res.status} ${res.statusText}, content-type: ${contentType || 'unknown'})`;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function formatUnexpectedApiBodySnippet(body, contentType) {
|
|
20
|
+
const raw = typeof body === 'string' ? body.trim() : '';
|
|
21
|
+
if (!raw) {
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
const normalizedContentType = String(contentType || '').toLowerCase();
|
|
25
|
+
const looksLikeHtml = normalizedContentType.includes('text/html')
|
|
26
|
+
|| /<!doctype\s+html|<html[\s>]|<head[\s>]|<body[\s>]/i.test(raw);
|
|
27
|
+
if (looksLikeHtml) {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
const singleLine = raw.replace(/\s+/g, ' ').trim();
|
|
31
|
+
if (!singleLine) {
|
|
32
|
+
return '';
|
|
33
|
+
}
|
|
34
|
+
return singleLine.length > 200
|
|
35
|
+
? `${singleLine.slice(0, 197)}...`
|
|
36
|
+
: singleLine;
|
|
37
|
+
}
|
|
38
|
+
|
|
19
39
|
function withPayloadTooLargeErrorCode(res, payload) {
|
|
20
40
|
if (res.status !== 413 || (payload && typeof payload === 'object' && payload.errorCode)) {
|
|
21
41
|
return payload;
|
|
@@ -29,7 +49,8 @@ export async function api(action, params = {}) {
|
|
|
29
49
|
if (contentType && !contentType.includes('application/json')) {
|
|
30
50
|
const body = await res.text();
|
|
31
51
|
const errorDetails = buildApiResponseContext(action, res, contentType);
|
|
32
|
-
const
|
|
52
|
+
const bodySnippet = formatUnexpectedApiBodySnippet(body, contentType);
|
|
53
|
+
const bodyDetails = bodySnippet ? `: ${bodySnippet}` : '';
|
|
33
54
|
throw new Error(`Unexpected non-JSON API response for ${errorDetails}${bodyDetails}`);
|
|
34
55
|
}
|
|
35
56
|
try {
|
|
@@ -9,6 +9,15 @@ export function createDashboardComputed() {
|
|
|
9
9
|
const removed = Number(stats.removed || 0);
|
|
10
10
|
return added > 0 || removed > 0;
|
|
11
11
|
},
|
|
12
|
+
configTemplateDiffHasChanges() {
|
|
13
|
+
const stats = this.configTemplateDiffStats || {};
|
|
14
|
+
const added = Number(stats.added || 0);
|
|
15
|
+
const removed = Number(stats.removed || 0);
|
|
16
|
+
if (this.configTemplateDiffHasChangesValue !== undefined && this.configTemplateDiffHasChangesValue !== null) {
|
|
17
|
+
return !!this.configTemplateDiffHasChangesValue;
|
|
18
|
+
}
|
|
19
|
+
return added > 0 || removed > 0;
|
|
20
|
+
},
|
|
12
21
|
claudeModelHasList() {
|
|
13
22
|
return this.claudeModelOptions.length > 0;
|
|
14
23
|
},
|
|
@@ -20,9 +29,6 @@ export function createDashboardComputed() {
|
|
|
20
29
|
}
|
|
21
30
|
return list;
|
|
22
31
|
},
|
|
23
|
-
hasLocalAndProxy() {
|
|
24
|
-
return false;
|
|
25
|
-
},
|
|
26
32
|
displayCurrentProvider() {
|
|
27
33
|
const switching = String(this.providerSwitchDisplayTarget || '').trim();
|
|
28
34
|
if (switching) return switching;
|
|
@@ -38,9 +44,13 @@ export function createDashboardComputed() {
|
|
|
38
44
|
const action = this.normalizeInstallAction(this.installCommandAction);
|
|
39
45
|
return targets.map((target) => {
|
|
40
46
|
const id = target && typeof target.id === 'string' ? target.id : '';
|
|
47
|
+
const termuxCommand = id === 'codex'
|
|
48
|
+
? this.getInstallCommand(id, action, 'termux')
|
|
49
|
+
: '';
|
|
41
50
|
return {
|
|
42
51
|
...target,
|
|
43
|
-
command: this.getInstallCommand(id, action)
|
|
52
|
+
command: this.getInstallCommand(id, action),
|
|
53
|
+
termuxCommand
|
|
44
54
|
};
|
|
45
55
|
});
|
|
46
56
|
},
|
|
@@ -55,6 +65,9 @@ export function createDashboardComputed() {
|
|
|
55
65
|
if (this.codexApplying || this.configTemplateApplying || this.openclawApplying) tasks.push('配置应用');
|
|
56
66
|
if (this.agentsSaving) tasks.push('AGENTS 保存');
|
|
57
67
|
if (this.skillsLoading || this.skillsDeleting || this.skillsScanningImports || this.skillsImporting || this.skillsZipImporting || this.skillsExporting) tasks.push('Skills 管理');
|
|
68
|
+
if (this.taskOrchestration && (this.taskOrchestration.loading || this.taskOrchestration.planning || this.taskOrchestration.running || this.taskOrchestration.queueAdding || this.taskOrchestration.queueStarting || this.taskOrchestration.retrying || this.taskOrchestration.selectedRunLoading)) {
|
|
69
|
+
tasks.push('任务编排');
|
|
70
|
+
}
|
|
58
71
|
return tasks.length ? tasks.join(' / ') : '空闲';
|
|
59
72
|
},
|
|
60
73
|
inspectorMessageSummary() {
|
|
@@ -98,15 +111,15 @@ export function createDashboardComputed() {
|
|
|
98
111
|
const platform = this.resolveInstallPlatform();
|
|
99
112
|
if (platform === 'win32') {
|
|
100
113
|
return [
|
|
101
|
-
'
|
|
102
|
-
'
|
|
103
|
-
'
|
|
114
|
+
this.t('docs.tip.win.1'),
|
|
115
|
+
this.t('docs.tip.win.2'),
|
|
116
|
+
this.t('docs.tip.win.3')
|
|
104
117
|
];
|
|
105
118
|
}
|
|
106
119
|
return [
|
|
107
|
-
'
|
|
108
|
-
'
|
|
109
|
-
'
|
|
120
|
+
this.t('docs.tip.unix.1'),
|
|
121
|
+
this.t('docs.tip.unix.2'),
|
|
122
|
+
this.t('docs.tip.unix.3')
|
|
110
123
|
];
|
|
111
124
|
}
|
|
112
125
|
};
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { createDashboardComputed } from './app.computed.dashboard.mjs';
|
|
2
|
+
import { createMainTabsComputed } from './app.computed.main-tabs.mjs';
|
|
2
3
|
import { createSessionComputed } from './app.computed.session.mjs';
|
|
3
4
|
import { createConfigModeComputed } from './config-mode.computed.mjs';
|
|
4
5
|
import { createSkillsComputed } from './skills.computed.mjs';
|
|
6
|
+
import { createPluginsComputed } from './plugins.computed.mjs';
|
|
5
7
|
|
|
6
8
|
export function createAppComputed() {
|
|
7
9
|
return {
|
|
8
10
|
...createSessionComputed(),
|
|
9
11
|
...createDashboardComputed(),
|
|
12
|
+
...createMainTabsComputed(),
|
|
10
13
|
...createSkillsComputed(),
|
|
14
|
+
...createPluginsComputed(),
|
|
11
15
|
...createConfigModeComputed()
|
|
12
16
|
};
|
|
13
17
|
}
|