Skip to content

Commit 1044986

Browse files
committed
fix(sessions): disable conversion and reduce standalone tokens
- Disable session conversion button in UI\n- Mark derived sessions in session-detail responses for UI gating\n- Disable conversion for derived sessions across sources\n- Limit standalone session plain output to reduce token usage
1 parent 92e8bf2 commit 1044986

6 files changed

Lines changed: 101 additions & 12 deletions

File tree

cli.js

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4515,7 +4515,10 @@ function listCodexSessions(limit, options = {}) {
45154515
titleReadBytes
45164516
});
45174517
if (summary) {
4518-
sessions.push(summary);
4518+
sessions.push({
4519+
...summary,
4520+
derived: isDerivedSessionFile(filePath)
4521+
});
45194522
}
45204523

45214524
if (sessions.length >= targetCount) {
@@ -4697,6 +4700,7 @@ function listClaudeSessions(limit, options = {}) {
46974700
models,
46984701
__messageCountExact: quickRecords.length > 0 && isSessionSummaryMessageCountExact(fileStat, summaryReadBytes),
46994702
filePath,
4703+
derived: isDerivedSessionFile(filePath),
47004704
keywords,
47014705
capabilities
47024706
});
@@ -4723,7 +4727,10 @@ function listClaudeSessions(limit, options = {}) {
47234727
titleReadBytes
47244728
});
47254729
if (summary) {
4726-
sessions.push(summary);
4730+
sessions.push({
4731+
...summary,
4732+
derived: isDerivedSessionFile(filePath)
4733+
});
47274734
}
47284735

47294736
if (sessions.length >= targetCount) {
@@ -4745,7 +4752,10 @@ function listClaudeSessions(limit, options = {}) {
47454752
titleReadBytes
47464753
});
47474754
if (summary) {
4748-
sessions.push(summary);
4755+
sessions.push({
4756+
...summary,
4757+
derived: isDerivedSessionFile(filePath)
4758+
});
47494759
}
47504760
seen.add(filePath);
47514761
}
@@ -6324,6 +6334,28 @@ function buildSessionPlainText(messages) {
63246334
return lines.join('\n');
63256335
}
63266336

6337+
function getDerivedSessionMetaPath(filePath) {
6338+
if (!filePath) return '';
6339+
const base = filePath.toLowerCase().endsWith('.jsonl')
6340+
? filePath.slice(0, -5)
6341+
: filePath;
6342+
return `${base}.meta.json`;
6343+
}
6344+
6345+
function isDerivedSessionFile(filePath) {
6346+
const metaPath = getDerivedSessionMetaPath(filePath);
6347+
if (!metaPath) return false;
6348+
try {
6349+
if (fs.existsSync(metaPath)) {
6350+
return true;
6351+
}
6352+
} catch (_) {
6353+
return false;
6354+
}
6355+
const base = path.basename(filePath || '', path.extname(filePath || ''));
6356+
return /-\d{8}-\d{6}-[0-9a-f]{6}$/i.test(base);
6357+
}
6358+
63276359
function resolveStateMaxMessages(state) {
63286360
if (!state || typeof state !== 'object') {
63296361
return MAX_EXPORT_MESSAGES;
@@ -6669,6 +6701,20 @@ async function readSessionDetail(params = {}) {
66696701
sessionId,
66706702
cwd: extracted.cwd || '',
66716703
updatedAt: extracted.updatedAt || '',
6704+
derived: (() => {
6705+
try {
6706+
const metaPath = filePath.toLowerCase().endsWith('.jsonl')
6707+
? `${filePath.slice(0, -5)}.meta.json`
6708+
: `${filePath}.meta.json`;
6709+
if (fs.existsSync(metaPath)) {
6710+
return true;
6711+
}
6712+
} catch (_) {
6713+
return false;
6714+
}
6715+
const base = path.basename(filePath || '', path.extname(filePath || ''));
6716+
return /-\d{8}-\d{6}-[0-9a-f]{6}$/i.test(base);
6717+
})(),
66726718
totalMessages: hasExactTotalMessages ? extracted.totalMessages : null,
66736719
clipped: typeof extracted.clipped === 'boolean'
66746720
? extracted.clipped
@@ -6696,6 +6742,15 @@ async function readSessionPlain(params = {}) {
66966742
return { error: 'Session file not found' };
66976743
}
66986744

6745+
const rawMaxMessages = params.maxMessages;
6746+
const maxMessages = rawMaxMessages === Infinity || rawMaxMessages === 'all'
6747+
? Infinity
6748+
: (
6749+
Number.isFinite(Number(rawMaxMessages))
6750+
? Math.max(1, Math.floor(Number(rawMaxMessages)))
6751+
: 50
6752+
);
6753+
66996754
let extracted;
67006755
if (source === 'gemini') {
67016756
let json;
@@ -6716,15 +6771,19 @@ async function readSessionPlain(params = {}) {
67166771
const text = extractMessageText(extractGeminiMessageText(entry.content ?? entry.message ?? entry.text));
67176772
if (!text && role !== 'system') continue;
67186773
messages.push({ role, text });
6774+
if (maxMessages !== Infinity && messages.length >= maxMessages) {
6775+
break;
6776+
}
67196777
}
67206778
extracted = {
67216779
sessionId: typeof json.sessionId === 'string' && json.sessionId.trim() ? json.sessionId.trim() : path.basename(filePath, '.json'),
67226780
cwd: typeof json.projectRoot === 'string' ? json.projectRoot : '',
6723-
messages
6781+
messages,
6782+
truncated: maxMessages !== Infinity && rawMessages.length > messages.length
67246783
};
67256784
} else {
67266785
try {
6727-
extracted = await extractMessagesFromFile(filePath, source, { maxMessages: Infinity });
6786+
extracted = await extractMessagesFromFile(filePath, source, { maxMessages });
67286787
} catch (e) {
67296788
extracted = null;
67306789
}
@@ -6738,7 +6797,7 @@ async function readSessionPlain(params = {}) {
67386797
if (fallbackRecords.length === 0) {
67396798
return { error: 'Session file is empty' };
67406799
}
6741-
extracted = extractMessagesFromRecords(fallbackRecords, source, { maxMessages: Infinity });
6800+
extracted = extractMessagesFromRecords(fallbackRecords, source, { maxMessages });
67426801
}
67436802
}
67446803

@@ -6757,7 +6816,8 @@ async function readSessionPlain(params = {}) {
67576816
sessionId,
67586817
title: sessionId,
67596818
filePath,
6760-
text
6819+
text,
6820+
clipped: maxMessages !== Infinity && !!(extracted && extracted.truncated)
67616821
};
67626822
}
67636823

@@ -6998,7 +7058,7 @@ async function convertSessionToDerived(params = {}) {
69987058
target,
69997059
truncated: !!extracted.truncated,
70007060
maxMessages: maxMessagesLabel,
7001-
session: summary || {
7061+
session: summary ? { ...summary, derived: true } : {
70027062
source: target,
70037063
sourceLabel: target === 'codex' ? 'Codex' : 'Claude Code',
70047064
sessionId: derivedSessionId,
@@ -7015,6 +7075,7 @@ async function convertSessionToDerived(params = {}) {
70157075
reasoningOutputTokens: 0,
70167076
__messageCountExact: true,
70177077
filePath: outputPath,
7078+
derived: true,
70187079
keywords: [],
70197080
capabilities: {}
70207081
}

tests/unit/web-ui-behavior-parity.test.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,8 @@ test('captured bundled app skeleton only exposes expected data key drift versus
487487
'openClaudeMdEditor'
488488
];
489489
allowedExtraCurrentMethodKeys.push(
490+
'isDerivedSession',
491+
'isDerivedSessionId',
490492
'resetConfigTemplateDiffState',
491493
'onConfigTemplateContentInput',
492494
'buildConfigTemplateDiffFingerprint',

web-ui/modules/app.methods.session-actions.mjs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,24 @@ export function createSessionActionMethods(options = {}) {
1010
} = options;
1111

1212
return {
13+
isDerivedSessionId(value) {
14+
const sessionId = typeof value === 'string' ? value.trim() : String(value || '');
15+
if (!sessionId) return false;
16+
return /-\d{8}-\d{6}-[0-9a-f]{6}$/i.test(sessionId);
17+
},
18+
19+
isDerivedSession(session) {
20+
if (!session || typeof session !== 'object') return false;
21+
if (session.derived === true) return true;
22+
if (this.isDerivedSessionId(session.sessionId)) return true;
23+
const rawFilePath = typeof session.filePath === 'string' ? session.filePath.trim() : '';
24+
if (!rawFilePath) return false;
25+
const normalized = rawFilePath.replace(/\\/g, '/');
26+
if (normalized.includes('/.codexmate/sessions/derived/')) return true;
27+
if (normalized.includes('/codexmate-derived/')) return true;
28+
return false;
29+
},
30+
1331
getSessionStandaloneContext() {
1432
try {
1533
const url = new URL(window.location.href);
@@ -20,6 +38,8 @@ export function createSessionActionMethods(options = {}) {
2038
const source = (url.searchParams.get('source') || '').trim().toLowerCase();
2139
const sessionId = (url.searchParams.get('sessionId') || url.searchParams.get('id') || '').trim();
2240
const filePath = (url.searchParams.get('filePath') || url.searchParams.get('path') || '').trim();
41+
const maxMessagesRaw = (url.searchParams.get('maxMessages') || '').trim();
42+
const maxMessages = Number(maxMessagesRaw);
2343
let error = '';
2444
if (!source) {
2545
error = '缺少 source 参数';
@@ -39,7 +59,8 @@ export function createSessionActionMethods(options = {}) {
3959
params: {
4060
source,
4161
sessionId,
42-
filePath
62+
filePath,
63+
maxMessages: Number.isFinite(maxMessages) && maxMessages > 0 ? Math.floor(maxMessages) : 0
4364
},
4465
error: ''
4566
};
@@ -67,7 +88,8 @@ export function createSessionActionMethods(options = {}) {
6788
sourceLabel,
6889
sessionId: context.params.sessionId,
6990
filePath: context.params.filePath,
70-
title: context.params.sessionId || context.params.filePath || '会话'
91+
title: context.params.sessionId || context.params.filePath || '会话',
92+
maxMessages: context.params.maxMessages || 50
7193
};
7294
this.activeSessionMessages = [];
7395
this.activeSessionDetailError = '';

web-ui/modules/app.methods.session-browser.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,8 @@ export function createSessionBrowserMethods(options = {}) {
686686
const res = await api('session-plain', {
687687
source: sessionSnapshot.source,
688688
sessionId: sessionSnapshot.sessionId,
689-
filePath: sessionSnapshot.filePath
689+
filePath: sessionSnapshot.filePath,
690+
maxMessages: sessionSnapshot.maxMessages || 50
690691
});
691692

692693
if (requestSeq !== this.sessionStandaloneRequestSeq) {

web-ui/partials/index/panel-sessions.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@
208208
<button
209209
class="btn-session-export"
210210
@click="convertSession(activeSession)"
211-
:disabled="!activeSession || sessionConverting[getSessionExportKey(activeSession)] || (activeSession.source !== 'codex' && activeSession.source !== 'claude')">
211+
:disabled="true">
212212
{{ (activeSession && sessionConverting[getSessionExportKey(activeSession)]) ? t('sessions.preview.converting') : t('sessions.preview.convert') }}
213213
</button>
214214
<button

web-ui/session-helpers.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,9 @@ export async function loadActiveSessionDetail(api, options = {}) {
422422
if (res.sourceLabel) {
423423
this.activeSession.sourceLabel = res.sourceLabel;
424424
}
425+
if (typeof res.derived === 'boolean') {
426+
this.activeSession.derived = res.derived;
427+
}
425428
if (res.sessionId) {
426429
this.activeSession.sessionId = res.sessionId;
427430
if (!this.activeSession.title) {

0 commit comments

Comments
 (0)