codexmate 0.0.25 → 0.0.26
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 +416 -413
- package/README.zh.md +349 -346
- package/cli/agents-files.js +224 -224
- package/cli/archive-helpers.js +446 -446
- package/cli/auth-profiles.js +375 -375
- package/cli/builtin-proxy.js +1079 -1079
- package/cli/claude-proxy.js +1022 -1022
- package/cli/config-bootstrap.js +384 -384
- package/cli/config-health.js +338 -338
- package/cli/doctor-core.js +903 -903
- package/cli/import-skills-url.js +356 -356
- package/cli/openai-bridge.js +997 -997
- package/cli/openclaw-config.js +629 -629
- package/cli/session-convert-args.js +65 -0
- package/cli/session-convert-io.js +82 -0
- package/cli/session-convert.js +43 -0
- package/cli/session-usage.concurrent.js +28 -28
- package/cli/session-usage.js +118 -118
- package/cli/session-usage.models.js +176 -176
- package/cli/skills.js +1141 -1141
- package/cli/zip-commands.js +510 -510
- package/cli.js +15218 -14736
- package/lib/automation.js +404 -404
- package/lib/cli-file-utils.js +151 -151
- package/lib/cli-models-utils.js +379 -379
- package/lib/cli-network-utils.js +190 -190
- package/lib/cli-path-utils.js +85 -85
- package/lib/cli-session-utils.js +121 -121
- package/lib/cli-sessions.js +417 -417
- package/lib/cli-utils.js +155 -155
- package/lib/download-artifacts.js +92 -92
- package/lib/mcp-stdio.js +453 -453
- package/lib/task-orchestrator.js +869 -869
- package/lib/text-diff.js +303 -303
- package/lib/workflow-engine.js +340 -340
- package/package.json +74 -74
- package/plugins/README.md +20 -20
- package/plugins/README.zh-CN.md +20 -20
- package/plugins/prompt-templates/comment-polish/index.mjs +25 -25
- package/plugins/prompt-templates/computed.mjs +253 -253
- package/plugins/prompt-templates/index.mjs +8 -8
- package/plugins/prompt-templates/manifest.mjs +15 -15
- package/plugins/prompt-templates/methods.mjs +619 -619
- package/plugins/prompt-templates/overview.mjs +90 -90
- package/plugins/prompt-templates/ownership.mjs +19 -19
- package/plugins/prompt-templates/rule-ack/index.mjs +21 -21
- package/plugins/prompt-templates/storage.mjs +64 -64
- package/plugins/registry.mjs +16 -16
- package/web-ui/app.js +625 -612
- package/web-ui/index.html +35 -35
- package/web-ui/logic.agents-diff.mjs +386 -386
- package/web-ui/logic.claude.mjs +168 -168
- package/web-ui/logic.mjs +5 -5
- package/web-ui/logic.runtime.mjs +128 -128
- package/web-ui/logic.session-convert.mjs +70 -0
- package/web-ui/logic.sessions.mjs +709 -614
- package/web-ui/modules/api.mjs +90 -90
- package/web-ui/modules/app.computed.dashboard.mjs +171 -128
- package/web-ui/modules/app.computed.index.mjs +17 -17
- package/web-ui/modules/app.computed.main-tabs.mjs +205 -205
- package/web-ui/modules/app.computed.session.mjs +946 -670
- package/web-ui/modules/app.constants.mjs +15 -15
- package/web-ui/modules/app.methods.agents.mjs +632 -632
- package/web-ui/modules/app.methods.claude-config.mjs +179 -174
- package/web-ui/modules/app.methods.codex-config.mjs +860 -784
- package/web-ui/modules/app.methods.index.mjs +92 -92
- package/web-ui/modules/app.methods.install.mjs +205 -205
- package/web-ui/modules/app.methods.navigation.mjs +743 -695
- package/web-ui/modules/app.methods.openclaw-core.mjs +814 -814
- package/web-ui/modules/app.methods.openclaw-editing.mjs +372 -372
- package/web-ui/modules/app.methods.openclaw-persist.mjs +369 -369
- package/web-ui/modules/app.methods.providers.mjs +404 -404
- package/web-ui/modules/app.methods.runtime.mjs +345 -345
- package/web-ui/modules/app.methods.session-actions.mjs +596 -544
- package/web-ui/modules/app.methods.session-browser.mjs +985 -722
- package/web-ui/modules/app.methods.session-timeline.mjs +479 -448
- package/web-ui/modules/app.methods.session-trash.mjs +424 -424
- package/web-ui/modules/app.methods.startup-claude.mjs +522 -417
- package/web-ui/modules/app.methods.task-orchestration.mjs +556 -556
- package/web-ui/modules/config-mode.computed.mjs +124 -124
- package/web-ui/modules/config-template-confirm-pref.mjs +33 -33
- package/web-ui/modules/i18n.dict.mjs +2113 -2055
- package/web-ui/modules/i18n.mjs +56 -56
- package/web-ui/modules/plugins.computed.mjs +3 -3
- package/web-ui/modules/plugins.methods.mjs +3 -3
- package/web-ui/modules/plugins.storage.mjs +11 -11
- package/web-ui/modules/sessions-filters-url.mjs +85 -85
- package/web-ui/modules/skills.computed.mjs +107 -107
- package/web-ui/modules/skills.methods.mjs +481 -481
- package/web-ui/partials/index/layout-footer.html +13 -13
- package/web-ui/partials/index/layout-header.html +475 -475
- package/web-ui/partials/index/modal-config-template-agents.html +174 -174
- package/web-ui/partials/index/modal-confirm-toast.html +32 -32
- package/web-ui/partials/index/modal-health-check.html +45 -45
- package/web-ui/partials/index/modal-openclaw-config.html +280 -280
- package/web-ui/partials/index/modal-skills.html +200 -200
- package/web-ui/partials/index/modals-basic.html +165 -165
- package/web-ui/partials/index/panel-config-claude.html +184 -179
- package/web-ui/partials/index/panel-config-codex.html +283 -283
- package/web-ui/partials/index/panel-config-openclaw.html +83 -83
- package/web-ui/partials/index/panel-dashboard.html +186 -186
- package/web-ui/partials/index/panel-docs.html +147 -147
- package/web-ui/partials/index/panel-market.html +177 -177
- package/web-ui/partials/index/panel-orchestration.html +391 -391
- package/web-ui/partials/index/panel-plugins.html +279 -279
- package/web-ui/partials/index/panel-sessions.html +326 -303
- package/web-ui/partials/index/panel-settings.html +258 -258
- package/web-ui/partials/index/panel-usage.html +342 -361
- package/web-ui/res/json5.min.js +1 -1
- package/web-ui/res/vue.global.prod.js +13 -13
- package/web-ui/session-helpers.mjs +576 -573
- package/web-ui/source-bundle.cjs +233 -233
- package/web-ui/styles/base-theme.css +268 -264
- package/web-ui/styles/controls-forms.css +423 -423
- package/web-ui/styles/dashboard.css +274 -274
- package/web-ui/styles/docs-panel.css +247 -247
- package/web-ui/styles/feedback.css +108 -108
- package/web-ui/styles/health-check-dialog.css +144 -144
- package/web-ui/styles/layout-shell.css +603 -603
- package/web-ui/styles/modals-core.css +464 -464
- package/web-ui/styles/navigation-panels.css +390 -390
- package/web-ui/styles/openclaw-structured.css +266 -266
- package/web-ui/styles/plugins-panel.css +523 -523
- package/web-ui/styles/responsive.css +454 -454
- package/web-ui/styles/sessions-list.css +415 -398
- package/web-ui/styles/sessions-preview.css +411 -411
- package/web-ui/styles/sessions-toolbar-trash.css +330 -268
- package/web-ui/styles/sessions-usage.css +945 -912
- package/web-ui/styles/settings-panel.css +166 -166
- package/web-ui/styles/skills-list.css +303 -303
- package/web-ui/styles/skills-market.css +406 -406
- package/web-ui/styles/task-orchestration.css +822 -822
- package/web-ui/styles/titles-cards.css +408 -408
- package/web-ui/styles.css +21 -21
- package/web-ui.html +17 -17
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const { parseMaxMessagesValue } = require('../lib/cli-session-utils');
|
|
5
|
+
|
|
6
|
+
function ensureDir(dirPath) {
|
|
7
|
+
if (!dirPath) return;
|
|
8
|
+
if (fs.existsSync(dirPath)) return;
|
|
9
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function resolveOutputPath(outputPath, defaultFileName) {
|
|
13
|
+
const fallback = path.resolve(process.cwd(), defaultFileName);
|
|
14
|
+
if (typeof outputPath !== 'string' || !outputPath.trim()) return fallback;
|
|
15
|
+
const trimmed = outputPath.trim();
|
|
16
|
+
const resolved = path.resolve(trimmed);
|
|
17
|
+
if (/[\\\/]$/.test(trimmed)) {
|
|
18
|
+
ensureDir(resolved);
|
|
19
|
+
return path.join(resolved, defaultFileName);
|
|
20
|
+
}
|
|
21
|
+
if (fs.existsSync(resolved)) {
|
|
22
|
+
try { if (fs.statSync(resolved).isDirectory()) return path.join(resolved, defaultFileName); } catch (_) {}
|
|
23
|
+
}
|
|
24
|
+
return resolved;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseArgs(args = []) {
|
|
28
|
+
const options = { from: '', to: '', sessionId: '', filePath: '', output: '', maxMessages: undefined };
|
|
29
|
+
const errors = [];
|
|
30
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
31
|
+
const arg = String(args[i] || '');
|
|
32
|
+
const next = args[i + 1] || '';
|
|
33
|
+
if (!arg) continue;
|
|
34
|
+
if (arg === '--from') { options.from = next; i += 1; continue; }
|
|
35
|
+
if (arg.startsWith('--from=')) { options.from = arg.slice(7); continue; }
|
|
36
|
+
if (arg === '--to') { options.to = next; i += 1; continue; }
|
|
37
|
+
if (arg.startsWith('--to=')) { options.to = arg.slice(5); continue; }
|
|
38
|
+
if (arg === '--session-id') { options.sessionId = next; i += 1; continue; }
|
|
39
|
+
if (arg.startsWith('--session-id=')) { options.sessionId = arg.slice(13); continue; }
|
|
40
|
+
if (arg === '--file') { options.filePath = next; i += 1; continue; }
|
|
41
|
+
if (arg.startsWith('--file=')) { options.filePath = arg.slice(7); continue; }
|
|
42
|
+
if (arg === '--output') { options.output = next; i += 1; continue; }
|
|
43
|
+
if (arg.startsWith('--output=')) { options.output = arg.slice(9); continue; }
|
|
44
|
+
if (arg === '--max-messages') { options.maxMessages = next; i += 1; continue; }
|
|
45
|
+
if (arg.startsWith('--max-messages=')) { options.maxMessages = arg.slice(15); continue; }
|
|
46
|
+
errors.push(`未知参数: ${arg}`);
|
|
47
|
+
}
|
|
48
|
+
options.from = String(options.from || '').trim().toLowerCase();
|
|
49
|
+
options.to = String(options.to || '').trim().toLowerCase();
|
|
50
|
+
if (options.from !== 'codex' && options.from !== 'claude') errors.push('参数 --from 仅支持 codex 或 claude');
|
|
51
|
+
if (options.to !== 'codex' && options.to !== 'claude') errors.push('参数 --to 仅支持 codex 或 claude');
|
|
52
|
+
if (options.from && options.to && options.from === options.to) errors.push('--from 与 --to 不能相同');
|
|
53
|
+
if (!options.from) errors.push('缺少 --from');
|
|
54
|
+
if (!options.to) errors.push('缺少 --to');
|
|
55
|
+
if (!options.sessionId && !options.filePath) errors.push('必须指定 --session-id 或 --file');
|
|
56
|
+
if (options.maxMessages !== undefined) {
|
|
57
|
+
const parsed = parseMaxMessagesValue(options.maxMessages);
|
|
58
|
+
if (parsed === null) errors.push('参数 --max-messages 无效');
|
|
59
|
+
else options.maxMessages = parsed === Infinity ? Infinity : Math.max(1, Math.floor(parsed));
|
|
60
|
+
}
|
|
61
|
+
return { options, error: errors.length ? errors.join(';') : '' };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = { ensureDir, resolveOutputPath, parseArgs };
|
|
65
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const readline = require('readline');
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
toIsoTime,
|
|
6
|
+
extractMessageText,
|
|
7
|
+
normalizeRole,
|
|
8
|
+
resolveMaxMessagesValue
|
|
9
|
+
} = require('../lib/cli-session-utils');
|
|
10
|
+
|
|
11
|
+
const { removeLeadingSystemMessage } = require('../lib/cli-sessions');
|
|
12
|
+
|
|
13
|
+
async function readSessionMessages(filePath, source, maxMessages) {
|
|
14
|
+
const limit = resolveMaxMessagesValue(maxMessages, 200);
|
|
15
|
+
const state = { sessionId: '', cwd: '', updatedAt: '', messages: [], truncated: false };
|
|
16
|
+
const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });
|
|
17
|
+
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
18
|
+
for await (const line of rl) {
|
|
19
|
+
const trimmed = String(line || '').trim();
|
|
20
|
+
if (!trimmed) continue;
|
|
21
|
+
let record;
|
|
22
|
+
try { record = JSON.parse(trimmed); } catch (_) { continue; }
|
|
23
|
+
const timestamp = toIsoTime(record.timestamp, '');
|
|
24
|
+
if (timestamp) state.updatedAt = timestamp;
|
|
25
|
+
if (source === 'codex' && record.type === 'session_meta' && record.payload) {
|
|
26
|
+
if (!state.sessionId && record.payload.id) state.sessionId = String(record.payload.id || '');
|
|
27
|
+
if (!state.cwd && record.payload.cwd) state.cwd = String(record.payload.cwd || '');
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (source === 'claude') {
|
|
31
|
+
if (!state.sessionId && record.sessionId) state.sessionId = String(record.sessionId || '');
|
|
32
|
+
if (!state.cwd && record.cwd) state.cwd = String(record.cwd || '');
|
|
33
|
+
}
|
|
34
|
+
let role = '';
|
|
35
|
+
let text = '';
|
|
36
|
+
if (source === 'codex' && record.type === 'response_item' && record.payload && record.payload.type === 'message') {
|
|
37
|
+
role = normalizeRole(record.payload.role);
|
|
38
|
+
text = extractMessageText(record.payload.content);
|
|
39
|
+
} else if (source === 'claude') {
|
|
40
|
+
role = normalizeRole(record.type);
|
|
41
|
+
text = extractMessageText(record.message ? record.message.content : '');
|
|
42
|
+
}
|
|
43
|
+
if (!role || !text) continue;
|
|
44
|
+
state.messages.push({ role, text, timestamp });
|
|
45
|
+
if (limit !== Infinity && state.messages.length > limit) {
|
|
46
|
+
state.messages.shift();
|
|
47
|
+
state.truncated = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
state.messages = removeLeadingSystemMessage(state.messages);
|
|
51
|
+
return state;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function buildTargetRecords(target, payload) {
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
const sessionId = String(payload.sessionId || '').trim();
|
|
57
|
+
const cwd = String(payload.cwd || '').trim();
|
|
58
|
+
const messages = Array.isArray(payload.messages) ? payload.messages : [];
|
|
59
|
+
if (target === 'codex') {
|
|
60
|
+
const records = [{ type: 'session_meta', timestamp: new Date(now).toISOString(), payload: { id: sessionId, cwd } }];
|
|
61
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
62
|
+
const m = messages[i] || {};
|
|
63
|
+
const role = normalizeRole(m.role);
|
|
64
|
+
const text = typeof m.text === 'string' ? m.text : '';
|
|
65
|
+
if (!role || !text) continue;
|
|
66
|
+
records.push({ type: 'response_item', timestamp: m.timestamp || new Date(now + i).toISOString(), payload: { type: 'message', role, content: text } });
|
|
67
|
+
}
|
|
68
|
+
return records;
|
|
69
|
+
}
|
|
70
|
+
const records = [];
|
|
71
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
72
|
+
const m = messages[i] || {};
|
|
73
|
+
const role = normalizeRole(m.role);
|
|
74
|
+
const text = typeof m.text === 'string' ? m.text : '';
|
|
75
|
+
if (!role || !text) continue;
|
|
76
|
+
records.push({ type: role, timestamp: m.timestamp || new Date(now + i).toISOString(), sessionId, cwd, message: { content: text } });
|
|
77
|
+
}
|
|
78
|
+
return records;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { readSessionMessages, buildTargetRecords };
|
|
82
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const { parseArgs, ensureDir, resolveOutputPath } = require('./session-convert-args');
|
|
5
|
+
const { readSessionMessages, buildTargetRecords } = require('./session-convert-io');
|
|
6
|
+
|
|
7
|
+
function printUsage() {
|
|
8
|
+
console.log('\n用法:');
|
|
9
|
+
console.log(' codexmate convert-session --from <codex|claude> --to <codex|claude> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function cmdConvertSession(args = [], deps = {}) {
|
|
13
|
+
const parsed = parseArgs(args);
|
|
14
|
+
if (parsed.error) {
|
|
15
|
+
console.error('错误:', parsed.error);
|
|
16
|
+
printUsage();
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
if (!deps || typeof deps.resolveSessionFilePath !== 'function') {
|
|
20
|
+
console.error('错误: convert-session missing resolver');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const opt = parsed.options;
|
|
24
|
+
const filePath = deps.resolveSessionFilePath(opt.from, opt.filePath, opt.sessionId);
|
|
25
|
+
if (!filePath) {
|
|
26
|
+
console.error('转换失败: Session file not found');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
const extracted = await readSessionMessages(filePath, opt.from, opt.maxMessages);
|
|
30
|
+
const sessionId = extracted.sessionId || opt.sessionId || path.basename(filePath, '.jsonl');
|
|
31
|
+
const safeSessionId = String(sessionId).replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
32
|
+
const records = buildTargetRecords(opt.to, { sessionId, cwd: extracted.cwd || '', messages: extracted.messages });
|
|
33
|
+
const jsonl = `${records.map(r => JSON.stringify(r)).join('\n')}\n`;
|
|
34
|
+
const outputPath = resolveOutputPath(opt.output, `${opt.to}-session-${safeSessionId}.jsonl`);
|
|
35
|
+
ensureDir(path.dirname(outputPath));
|
|
36
|
+
fs.writeFileSync(outputPath, jsonl, 'utf-8');
|
|
37
|
+
console.log('\n✓ 会话已转换:', outputPath);
|
|
38
|
+
if (extracted.truncated) console.log('! 已截断: 可使用 --max-messages=all');
|
|
39
|
+
console.log();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { cmdConvertSession };
|
|
43
|
+
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
function createConcurrencyLimiter(maxConcurrency) {
|
|
2
|
-
const max = Number.isFinite(Number(maxConcurrency))
|
|
3
|
-
? Math.max(1, Math.floor(Number(maxConcurrency)))
|
|
4
|
-
: 8;
|
|
5
|
-
let active = 0;
|
|
6
|
-
const queue = [];
|
|
7
|
-
const next = () => {
|
|
8
|
-
const resolve = queue.shift();
|
|
9
|
-
if (resolve) resolve();
|
|
10
|
-
};
|
|
11
|
-
return async (task) => {
|
|
12
|
-
if (active >= max) {
|
|
13
|
-
await new Promise((resolve) => queue.push(resolve));
|
|
14
|
-
}
|
|
15
|
-
active += 1;
|
|
16
|
-
try {
|
|
17
|
-
return await task();
|
|
18
|
-
} finally {
|
|
19
|
-
active -= 1;
|
|
20
|
-
next();
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
module.exports = {
|
|
26
|
-
createConcurrencyLimiter
|
|
27
|
-
};
|
|
28
|
-
|
|
1
|
+
function createConcurrencyLimiter(maxConcurrency) {
|
|
2
|
+
const max = Number.isFinite(Number(maxConcurrency))
|
|
3
|
+
? Math.max(1, Math.floor(Number(maxConcurrency)))
|
|
4
|
+
: 8;
|
|
5
|
+
let active = 0;
|
|
6
|
+
const queue = [];
|
|
7
|
+
const next = () => {
|
|
8
|
+
const resolve = queue.shift();
|
|
9
|
+
if (resolve) resolve();
|
|
10
|
+
};
|
|
11
|
+
return async (task) => {
|
|
12
|
+
if (active >= max) {
|
|
13
|
+
await new Promise((resolve) => queue.push(resolve));
|
|
14
|
+
}
|
|
15
|
+
active += 1;
|
|
16
|
+
try {
|
|
17
|
+
return await task();
|
|
18
|
+
} finally {
|
|
19
|
+
active -= 1;
|
|
20
|
+
next();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = {
|
|
26
|
+
createConcurrencyLimiter
|
|
27
|
+
};
|
|
28
|
+
|
package/cli/session-usage.js
CHANGED
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
const { createConcurrencyLimiter } = require('./session-usage.concurrent');
|
|
2
|
-
const { normalizeSessionModelList, createSessionModelsFileReader } = require('./session-usage.models');
|
|
3
|
-
|
|
4
|
-
async function listSessionUsageCore(params = {}, deps = {}) {
|
|
5
|
-
const {
|
|
6
|
-
fs,
|
|
7
|
-
listSessionBrowse,
|
|
8
|
-
parseCodexSessionSummary,
|
|
9
|
-
parseClaudeSessionSummary,
|
|
10
|
-
parseCodeBuddySessionSummary,
|
|
11
|
-
parseGeminiSessionSummary,
|
|
12
|
-
MAX_SESSION_USAGE_LIST_SIZE,
|
|
13
|
-
SESSION_BROWSE_SUMMARY_READ_BYTES
|
|
14
|
-
} = deps;
|
|
15
|
-
|
|
16
|
-
const source = params.source === 'codex' || params.source === 'claude' || params.source === 'gemini' || params.source === 'codebuddy'
|
|
17
|
-
? params.source
|
|
18
|
-
: 'all';
|
|
19
|
-
const rawLimit = Number(params.limit);
|
|
20
|
-
const limit = Number.isFinite(rawLimit)
|
|
21
|
-
? Math.max(1, Math.min(rawLimit, MAX_SESSION_USAGE_LIST_SIZE))
|
|
22
|
-
: MAX_SESSION_USAGE_LIST_SIZE;
|
|
23
|
-
|
|
24
|
-
const sessions = await listSessionBrowse({
|
|
25
|
-
source,
|
|
26
|
-
limit,
|
|
27
|
-
forceRefresh: !!params.forceRefresh
|
|
28
|
-
});
|
|
29
|
-
if (!Array.isArray(sessions) || sessions.length === 0) {
|
|
30
|
-
return [];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const { readSessionModelsFromFile } = createSessionModelsFileReader(fs, {
|
|
34
|
-
concurrency: 32,
|
|
35
|
-
maxEntries: 1500,
|
|
36
|
-
probeHeadBytes: 128 * 1024,
|
|
37
|
-
probeTailBytes: 128 * 1024
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// CPU/IO 优化策略(面向 2000 会话):
|
|
41
|
-
// 1) 优先使用 listSessionBrowse 返回的 model/models(零 I/O)
|
|
42
|
-
// 2) 仅当缺少模型名时才读取/解析文件(必要时全文件扫描)
|
|
43
|
-
const limitNormalize = createConcurrencyLimiter(64);
|
|
44
|
-
const normalizedSessions = await Promise.all(
|
|
45
|
-
sessions.map((item) => limitNormalize(async () => {
|
|
46
|
-
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
const normalized = { ...item };
|
|
50
|
-
delete normalized.__messageCountExact;
|
|
51
|
-
|
|
52
|
-
const baseModels = normalizeSessionModelList([
|
|
53
|
-
...(Array.isArray(normalized.models) ? normalized.models : []),
|
|
54
|
-
normalized.model,
|
|
55
|
-
normalized.modelName,
|
|
56
|
-
normalized.modelId
|
|
57
|
-
]);
|
|
58
|
-
if (baseModels.length > 0) {
|
|
59
|
-
normalized.models = baseModels;
|
|
60
|
-
normalized.model = baseModels[0];
|
|
61
|
-
return normalized;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const filePath = typeof normalized.filePath === 'string' ? normalized.filePath.trim() : '';
|
|
65
|
-
if (!filePath) {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// 快速路径:全文件正则扫描(并发 + 缓存)。只对“缺模型”的会话触发。
|
|
70
|
-
const fullFileModels = await readSessionModelsFromFile(filePath);
|
|
71
|
-
if (fullFileModels.length > 0) {
|
|
72
|
-
normalized.models = fullFileModels;
|
|
73
|
-
normalized.model = fullFileModels[0];
|
|
74
|
-
return normalized;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// 兜底:摘要解析(可能补 provider 等字段)
|
|
78
|
-
const summaryOptions = {
|
|
79
|
-
summaryReadBytes: SESSION_BROWSE_SUMMARY_READ_BYTES,
|
|
80
|
-
titleReadBytes: SESSION_BROWSE_SUMMARY_READ_BYTES
|
|
81
|
-
};
|
|
82
|
-
let summary = null;
|
|
83
|
-
try {
|
|
84
|
-
summary = normalized.source === 'claude'
|
|
85
|
-
? parseClaudeSessionSummary(filePath, summaryOptions)
|
|
86
|
-
: (normalized.source === 'gemini'
|
|
87
|
-
? parseGeminiSessionSummary(filePath, summaryOptions)
|
|
88
|
-
: (normalized.source === 'codebuddy'
|
|
89
|
-
? parseCodeBuddySessionSummary(filePath, summaryOptions)
|
|
90
|
-
: parseCodexSessionSummary(filePath, summaryOptions)));
|
|
91
|
-
} catch (_) {
|
|
92
|
-
summary = null;
|
|
93
|
-
}
|
|
94
|
-
if (!summary || typeof summary !== 'object' || Array.isArray(summary)) {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
const summaryModels = normalizeSessionModelList([
|
|
98
|
-
...(Array.isArray(summary.models) ? summary.models : []),
|
|
99
|
-
summary.model
|
|
100
|
-
]);
|
|
101
|
-
if (summaryModels.length === 0) {
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
normalized.models = summaryModels;
|
|
105
|
-
normalized.model = summaryModels[0];
|
|
106
|
-
if ((!normalized.provider || !String(normalized.provider).trim()) && typeof summary.provider === 'string' && summary.provider.trim()) {
|
|
107
|
-
normalized.provider = summary.provider.trim();
|
|
108
|
-
}
|
|
109
|
-
return normalized;
|
|
110
|
-
}))
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
return normalizedSessions.filter(Boolean);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
module.exports = {
|
|
117
|
-
listSessionUsageCore
|
|
118
|
-
};
|
|
1
|
+
const { createConcurrencyLimiter } = require('./session-usage.concurrent');
|
|
2
|
+
const { normalizeSessionModelList, createSessionModelsFileReader } = require('./session-usage.models');
|
|
3
|
+
|
|
4
|
+
async function listSessionUsageCore(params = {}, deps = {}) {
|
|
5
|
+
const {
|
|
6
|
+
fs,
|
|
7
|
+
listSessionBrowse,
|
|
8
|
+
parseCodexSessionSummary,
|
|
9
|
+
parseClaudeSessionSummary,
|
|
10
|
+
parseCodeBuddySessionSummary,
|
|
11
|
+
parseGeminiSessionSummary,
|
|
12
|
+
MAX_SESSION_USAGE_LIST_SIZE,
|
|
13
|
+
SESSION_BROWSE_SUMMARY_READ_BYTES
|
|
14
|
+
} = deps;
|
|
15
|
+
|
|
16
|
+
const source = params.source === 'codex' || params.source === 'claude' || params.source === 'gemini' || params.source === 'codebuddy'
|
|
17
|
+
? params.source
|
|
18
|
+
: 'all';
|
|
19
|
+
const rawLimit = Number(params.limit);
|
|
20
|
+
const limit = Number.isFinite(rawLimit)
|
|
21
|
+
? Math.max(1, Math.min(rawLimit, MAX_SESSION_USAGE_LIST_SIZE))
|
|
22
|
+
: MAX_SESSION_USAGE_LIST_SIZE;
|
|
23
|
+
|
|
24
|
+
const sessions = await listSessionBrowse({
|
|
25
|
+
source,
|
|
26
|
+
limit,
|
|
27
|
+
forceRefresh: !!params.forceRefresh
|
|
28
|
+
});
|
|
29
|
+
if (!Array.isArray(sessions) || sessions.length === 0) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { readSessionModelsFromFile } = createSessionModelsFileReader(fs, {
|
|
34
|
+
concurrency: 32,
|
|
35
|
+
maxEntries: 1500,
|
|
36
|
+
probeHeadBytes: 128 * 1024,
|
|
37
|
+
probeTailBytes: 128 * 1024
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// CPU/IO 优化策略(面向 2000 会话):
|
|
41
|
+
// 1) 优先使用 listSessionBrowse 返回的 model/models(零 I/O)
|
|
42
|
+
// 2) 仅当缺少模型名时才读取/解析文件(必要时全文件扫描)
|
|
43
|
+
const limitNormalize = createConcurrencyLimiter(64);
|
|
44
|
+
const normalizedSessions = await Promise.all(
|
|
45
|
+
sessions.map((item) => limitNormalize(async () => {
|
|
46
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const normalized = { ...item };
|
|
50
|
+
delete normalized.__messageCountExact;
|
|
51
|
+
|
|
52
|
+
const baseModels = normalizeSessionModelList([
|
|
53
|
+
...(Array.isArray(normalized.models) ? normalized.models : []),
|
|
54
|
+
normalized.model,
|
|
55
|
+
normalized.modelName,
|
|
56
|
+
normalized.modelId
|
|
57
|
+
]);
|
|
58
|
+
if (baseModels.length > 0) {
|
|
59
|
+
normalized.models = baseModels;
|
|
60
|
+
normalized.model = baseModels[0];
|
|
61
|
+
return normalized;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const filePath = typeof normalized.filePath === 'string' ? normalized.filePath.trim() : '';
|
|
65
|
+
if (!filePath) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 快速路径:全文件正则扫描(并发 + 缓存)。只对“缺模型”的会话触发。
|
|
70
|
+
const fullFileModels = await readSessionModelsFromFile(filePath);
|
|
71
|
+
if (fullFileModels.length > 0) {
|
|
72
|
+
normalized.models = fullFileModels;
|
|
73
|
+
normalized.model = fullFileModels[0];
|
|
74
|
+
return normalized;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 兜底:摘要解析(可能补 provider 等字段)
|
|
78
|
+
const summaryOptions = {
|
|
79
|
+
summaryReadBytes: SESSION_BROWSE_SUMMARY_READ_BYTES,
|
|
80
|
+
titleReadBytes: SESSION_BROWSE_SUMMARY_READ_BYTES
|
|
81
|
+
};
|
|
82
|
+
let summary = null;
|
|
83
|
+
try {
|
|
84
|
+
summary = normalized.source === 'claude'
|
|
85
|
+
? parseClaudeSessionSummary(filePath, summaryOptions)
|
|
86
|
+
: (normalized.source === 'gemini'
|
|
87
|
+
? parseGeminiSessionSummary(filePath, summaryOptions)
|
|
88
|
+
: (normalized.source === 'codebuddy'
|
|
89
|
+
? parseCodeBuddySessionSummary(filePath, summaryOptions)
|
|
90
|
+
: parseCodexSessionSummary(filePath, summaryOptions)));
|
|
91
|
+
} catch (_) {
|
|
92
|
+
summary = null;
|
|
93
|
+
}
|
|
94
|
+
if (!summary || typeof summary !== 'object' || Array.isArray(summary)) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const summaryModels = normalizeSessionModelList([
|
|
98
|
+
...(Array.isArray(summary.models) ? summary.models : []),
|
|
99
|
+
summary.model
|
|
100
|
+
]);
|
|
101
|
+
if (summaryModels.length === 0) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
normalized.models = summaryModels;
|
|
105
|
+
normalized.model = summaryModels[0];
|
|
106
|
+
if ((!normalized.provider || !String(normalized.provider).trim()) && typeof summary.provider === 'string' && summary.provider.trim()) {
|
|
107
|
+
normalized.provider = summary.provider.trim();
|
|
108
|
+
}
|
|
109
|
+
return normalized;
|
|
110
|
+
}))
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
return normalizedSessions.filter(Boolean);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
listSessionUsageCore
|
|
118
|
+
};
|