chore: rebuild CentOS7 release package
This commit is contained in:
Binary file not shown.
@@ -18,15 +18,38 @@ function createCodexRolloutStore(deps) {
|
|||||||
turn.content = turn.content ? `${turn.content}\n\n${text}` : text;
|
turn.content = turn.content ? `${turn.content}\n\n${text}` : text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractCcwebSourceConversation(text) {
|
||||||
|
const match = String(text || '').match(/^来自「([^」]+)」对话(ID:\s*([0-9a-fA-F-]{36}))的消息:/);
|
||||||
|
if (!match) return null;
|
||||||
|
return { title: match[1], id: match[2].toLowerCase() };
|
||||||
|
}
|
||||||
|
|
||||||
function parseCodexRolloutLines(lines) {
|
function parseCodexRolloutLines(lines) {
|
||||||
const messages = [];
|
const messages = [];
|
||||||
const pendingToolCalls = new Map();
|
const pendingToolCalls = new Map();
|
||||||
const meta = { threadId: null, cwd: null, title: '', updatedAt: null, cliVersion: null, source: null };
|
const meta = {
|
||||||
|
threadId: null,
|
||||||
|
cwd: null,
|
||||||
|
title: '',
|
||||||
|
updatedAt: null,
|
||||||
|
cliVersion: null,
|
||||||
|
source: null,
|
||||||
|
sourceConversationId: null,
|
||||||
|
sourceConversationTitle: '',
|
||||||
|
};
|
||||||
const totalUsage = { inputTokens: 0, cachedInputTokens: 0, outputTokens: 0 };
|
const totalUsage = { inputTokens: 0, cachedInputTokens: 0, outputTokens: 0 };
|
||||||
let currentAssistant = null;
|
let currentAssistant = null;
|
||||||
let sawRealUserMessage = false;
|
let sawRealUserMessage = false;
|
||||||
const fallbackUserMessages = [];
|
const fallbackUserMessages = [];
|
||||||
|
|
||||||
|
function rememberSourceConversation(text) {
|
||||||
|
if (meta.sourceConversationId) return;
|
||||||
|
const sourceConversation = extractCcwebSourceConversation(text);
|
||||||
|
if (!sourceConversation) return;
|
||||||
|
meta.sourceConversationId = sourceConversation.id;
|
||||||
|
meta.sourceConversationTitle = sourceConversation.title;
|
||||||
|
}
|
||||||
|
|
||||||
function ensureAssistant(ts) {
|
function ensureAssistant(ts) {
|
||||||
if (!currentAssistant) {
|
if (!currentAssistant) {
|
||||||
currentAssistant = { role: 'assistant', content: '', toolCalls: [], timestamp: ts || null };
|
currentAssistant = { role: 'assistant', content: '', toolCalls: [], timestamp: ts || null };
|
||||||
@@ -81,6 +104,7 @@ function createCodexRolloutStore(deps) {
|
|||||||
if (text) {
|
if (text) {
|
||||||
sawRealUserMessage = true;
|
sawRealUserMessage = true;
|
||||||
flushAssistant();
|
flushAssistant();
|
||||||
|
rememberSourceConversation(text);
|
||||||
if (!meta.title) meta.title = text.slice(0, 80).replace(/\n/g, ' ');
|
if (!meta.title) meta.title = text.slice(0, 80).replace(/\n/g, ' ');
|
||||||
messages.push({ role: 'user', content: text, timestamp: ts });
|
messages.push({ role: 'user', content: text, timestamp: ts });
|
||||||
}
|
}
|
||||||
@@ -103,6 +127,7 @@ function createCodexRolloutStore(deps) {
|
|||||||
} else if (payload.role === 'user' && !sawRealUserMessage) {
|
} else if (payload.role === 'user' && !sawRealUserMessage) {
|
||||||
const text = extractCodexMessageText(payload.content);
|
const text = extractCodexMessageText(payload.content);
|
||||||
if (text.trim()) {
|
if (text.trim()) {
|
||||||
|
rememberSourceConversation(text);
|
||||||
fallbackUserMessages.push({ role: 'user', content: text, timestamp: ts });
|
fallbackUserMessages.push({ role: 'user', content: text, timestamp: ts });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9878,8 +9878,32 @@
|
|||||||
// --- Import Native Session Modal ---
|
// --- Import Native Session Modal ---
|
||||||
let _onNativeSessions = null;
|
let _onNativeSessions = null;
|
||||||
|
|
||||||
|
function appendImportVisibilityToggle(body, options) {
|
||||||
|
const hiddenCount = Number(options?.hiddenCount || 0);
|
||||||
|
if (!body || hiddenCount <= 0) return;
|
||||||
|
const row = document.createElement('label');
|
||||||
|
row.className = 'import-filter-row';
|
||||||
|
row.innerHTML = `
|
||||||
|
<span class="import-filter-copy">
|
||||||
|
<span class="import-filter-title">显示已导入会话</span>
|
||||||
|
<span class="import-filter-meta">已隐藏 ${hiddenCount} 个 cc-web 已存在的会话</span>
|
||||||
|
</span>
|
||||||
|
<span class="settings-switch">
|
||||||
|
<input type="checkbox" ${options.showImported ? 'checked' : ''}>
|
||||||
|
<span class="settings-switch-track" aria-hidden="true">
|
||||||
|
<span class="settings-switch-thumb"></span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
const input = row.querySelector('input');
|
||||||
|
input.addEventListener('change', () => options.onToggle(!!input.checked));
|
||||||
|
body.appendChild(row);
|
||||||
|
}
|
||||||
|
|
||||||
function showImportSessionModal() {
|
function showImportSessionModal() {
|
||||||
if (currentAgent !== 'claude') return;
|
if (currentAgent !== 'claude') return;
|
||||||
|
let nativeGroups = [];
|
||||||
|
let showImported = false;
|
||||||
const overlay = document.createElement('div');
|
const overlay = document.createElement('div');
|
||||||
overlay.className = 'modal-overlay';
|
overlay.className = 'modal-overlay';
|
||||||
overlay.id = 'import-session-overlay';
|
overlay.id = 'import-session-overlay';
|
||||||
@@ -9907,15 +9931,40 @@
|
|||||||
overlay.querySelector('#is-close-btn').addEventListener('click', close);
|
overlay.querySelector('#is-close-btn').addEventListener('click', close);
|
||||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
|
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
|
||||||
|
|
||||||
_onNativeSessions = (groups) => {
|
function renderNativeSessions() {
|
||||||
const body = overlay.querySelector('#is-body');
|
const body = overlay.querySelector('#is-body');
|
||||||
if (!body) return;
|
if (!body) return;
|
||||||
if (!groups || groups.length === 0) {
|
if (!nativeGroups || nativeGroups.length === 0) {
|
||||||
body.innerHTML = `${buildAgentContextCard('claude', '从 Claude 原生历史导入', '读取 ~/.claude/projects/ 下的会话文件,恢复对话文本与工具调用,并保留 Claude 侧续接上下文。')}<div class="modal-empty">未找到本地 CLI 会话</div>`;
|
body.innerHTML = `${buildAgentContextCard('claude', '从 Claude 原生历史导入', '读取 ~/.claude/projects/ 下的会话文件,恢复对话文本与工具调用,并保留 Claude 侧续接上下文。')}<div class="modal-empty">未找到本地 CLI 会话</div>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
body.innerHTML = buildAgentContextCard('claude', '从 Claude 原生历史导入', '读取 ~/.claude/projects/ 下的会话文件,恢复对话文本与工具调用,并保留 Claude 侧续接上下文。');
|
body.innerHTML = buildAgentContextCard('claude', '从 Claude 原生历史导入', '读取 ~/.claude/projects/ 下的会话文件,恢复对话文本与工具调用,并保留 Claude 侧续接上下文。');
|
||||||
for (const group of groups) {
|
const hiddenCount = nativeGroups.reduce((sum, group) => (
|
||||||
|
sum + (Array.isArray(group.sessions) ? group.sessions.filter((sess) => sess.alreadyImported).length : 0)
|
||||||
|
), 0);
|
||||||
|
appendImportVisibilityToggle(body, {
|
||||||
|
hiddenCount,
|
||||||
|
showImported,
|
||||||
|
onToggle: (next) => {
|
||||||
|
showImported = next;
|
||||||
|
renderNativeSessions();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const visibleGroups = nativeGroups
|
||||||
|
.map((group) => ({
|
||||||
|
...group,
|
||||||
|
sessions: showImported
|
||||||
|
? (group.sessions || [])
|
||||||
|
: (group.sessions || []).filter((sess) => !sess.alreadyImported),
|
||||||
|
}))
|
||||||
|
.filter((group) => group.sessions.length > 0);
|
||||||
|
if (visibleGroups.length === 0) {
|
||||||
|
body.insertAdjacentHTML('beforeend', `<div class="modal-empty">已隐藏 ${hiddenCount} 个已导入会话,打开上方开关可查看。</div>`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const group of visibleGroups) {
|
||||||
const groupEl = document.createElement('div');
|
const groupEl = document.createElement('div');
|
||||||
groupEl.className = 'import-group';
|
groupEl.className = 'import-group';
|
||||||
// Convert slug dir to readable path
|
// Convert slug dir to readable path
|
||||||
@@ -9959,6 +10008,11 @@
|
|||||||
}
|
}
|
||||||
body.appendChild(groupEl);
|
body.appendChild(groupEl);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onNativeSessions = (groups) => {
|
||||||
|
nativeGroups = Array.isArray(groups) ? groups : [];
|
||||||
|
renderNativeSessions();
|
||||||
};
|
};
|
||||||
|
|
||||||
send({ type: 'list_native_sessions' });
|
send({ type: 'list_native_sessions' });
|
||||||
@@ -9967,6 +10021,8 @@
|
|||||||
function showImportCodexSessionModal() {
|
function showImportCodexSessionModal() {
|
||||||
if (!isCodexLikeAgent(currentAgent)) return;
|
if (!isCodexLikeAgent(currentAgent)) return;
|
||||||
const importAgent = currentAgent;
|
const importAgent = currentAgent;
|
||||||
|
let codexItems = [];
|
||||||
|
let showImported = false;
|
||||||
const label = AGENT_LABELS[importAgent] || 'Codex';
|
const label = AGENT_LABELS[importAgent] || 'Codex';
|
||||||
const contextTitle = importAgent === 'codexapp'
|
const contextTitle = importAgent === 'codexapp'
|
||||||
? '从 Codex App rollout 历史导入'
|
? '从 Codex App rollout 历史导入'
|
||||||
@@ -10001,16 +10057,32 @@
|
|||||||
overlay.querySelector('#ics-close-btn').addEventListener('click', close);
|
overlay.querySelector('#ics-close-btn').addEventListener('click', close);
|
||||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
|
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
|
||||||
|
|
||||||
_onCodexSessions = (items) => {
|
function renderCodexSessions() {
|
||||||
const body = overlay.querySelector('#ics-body');
|
const body = overlay.querySelector('#ics-body');
|
||||||
if (!body) return;
|
if (!body) return;
|
||||||
if (!items || items.length === 0) {
|
if (!codexItems || codexItems.length === 0) {
|
||||||
body.innerHTML = `${buildAgentContextCard(importAgent, contextTitle, contextCopy)}<div class="modal-empty">未找到本地 ${escapeHtml(label)} 会话</div>`;
|
body.innerHTML = `${buildAgentContextCard(importAgent, contextTitle, contextCopy)}<div class="modal-empty">未找到本地 ${escapeHtml(label)} 会话</div>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.innerHTML = buildAgentContextCard(importAgent, contextTitle, contextCopy);
|
body.innerHTML = buildAgentContextCard(importAgent, contextTitle, contextCopy);
|
||||||
items.forEach((sess) => {
|
const hiddenCount = codexItems.filter((sess) => sess.alreadyImported).length;
|
||||||
|
appendImportVisibilityToggle(body, {
|
||||||
|
hiddenCount,
|
||||||
|
showImported,
|
||||||
|
onToggle: (next) => {
|
||||||
|
showImported = next;
|
||||||
|
renderCodexSessions();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const visibleItems = showImported ? codexItems : codexItems.filter((sess) => !sess.alreadyImported);
|
||||||
|
if (visibleItems.length === 0) {
|
||||||
|
body.insertAdjacentHTML('beforeend', `<div class="modal-empty">已隐藏 ${hiddenCount} 个已导入会话,打开上方开关可查看。</div>`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
visibleItems.forEach((sess) => {
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = 'import-item';
|
item.className = 'import-item';
|
||||||
|
|
||||||
@@ -10043,6 +10115,12 @@
|
|||||||
source.textContent = sess.source;
|
source.textContent = sess.source;
|
||||||
tags.appendChild(source);
|
tags.appendChild(source);
|
||||||
}
|
}
|
||||||
|
if ((sess.duplicateCount || 0) > 1) {
|
||||||
|
const merged = document.createElement('span');
|
||||||
|
merged.className = 'import-item-tag';
|
||||||
|
merged.textContent = `合并 ${sess.duplicateCount} 条`;
|
||||||
|
tags.appendChild(merged);
|
||||||
|
}
|
||||||
|
|
||||||
info.appendChild(titleEl);
|
info.appendChild(titleEl);
|
||||||
info.appendChild(meta);
|
info.appendChild(meta);
|
||||||
@@ -10064,6 +10142,11 @@
|
|||||||
item.appendChild(btn);
|
item.appendChild(btn);
|
||||||
body.appendChild(item);
|
body.appendChild(item);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_onCodexSessions = (items) => {
|
||||||
|
codexItems = Array.isArray(items) ? items : [];
|
||||||
|
renderCodexSessions();
|
||||||
};
|
};
|
||||||
|
|
||||||
send({ type: 'list_codex_sessions', agent: importAgent });
|
send({ type: 'list_codex_sessions', agent: importAgent });
|
||||||
|
|||||||
@@ -5396,6 +5396,33 @@ html[data-theme='coolvibe'] .settings-back:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* === Import Session List === */
|
/* === Import Session List === */
|
||||||
|
.import-filter-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin: 12px 0 14px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.import-filter-copy {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.import-filter-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
.import-filter-meta {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
.import-group {
|
.import-group {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -515,6 +515,17 @@ function assertFrontendGenerationControlsContract() {
|
|||||||
source.includes("send({ type: 'import_codex_session', agent: importAgent"),
|
source.includes("send({ type: 'import_codex_session', agent: importAgent"),
|
||||||
'Frontend Codex import modal should pass the selected Codex-like agent'
|
'Frontend Codex import modal should pass the selected Codex-like agent'
|
||||||
);
|
);
|
||||||
|
assert(
|
||||||
|
source.includes('function appendImportVisibilityToggle') &&
|
||||||
|
source.includes('显示已导入会话') &&
|
||||||
|
source.includes('cc-web 已存在的会话'),
|
||||||
|
'Frontend import modal should expose a toggle for already imported sessions'
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
source.includes('(group.sessions || []).filter((sess) => !sess.alreadyImported)') &&
|
||||||
|
source.includes('codexItems.filter((sess) => !sess.alreadyImported)'),
|
||||||
|
'Frontend import modal should hide already imported sessions by default'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertFrontendComposerMcpContract() {
|
function assertFrontendComposerMcpContract() {
|
||||||
@@ -697,6 +708,32 @@ async function main() {
|
|||||||
source: 'vscode',
|
source: 'vscode',
|
||||||
fileStamp: '2026-03-12T00-00-10',
|
fileStamp: '2026-03-12T00-00-10',
|
||||||
});
|
});
|
||||||
|
const duplicateSourceConversationId = '11111111-1111-4111-8111-111111111111';
|
||||||
|
const duplicateSourceConversationTitle = '你能看下 00a7cbc2-d0c3-457f-a262-aa5a5859fa54 这个对话么, 你来评估下,这个对话中';
|
||||||
|
createFakeCodexHistory(homeDir, {
|
||||||
|
threadId: 'codexapp-duplicate-thread-a',
|
||||||
|
cwd: '/tmp/project-c',
|
||||||
|
userText: `来自「${duplicateSourceConversationTitle}」对话(ID: ${duplicateSourceConversationId})的消息:\n\n旧候选`,
|
||||||
|
answerText: 'duplicate import answer a',
|
||||||
|
source: 'vscode',
|
||||||
|
fileStamp: '2026-03-12T00-00-20',
|
||||||
|
});
|
||||||
|
createFakeCodexHistory(homeDir, {
|
||||||
|
threadId: 'codexapp-duplicate-thread-b',
|
||||||
|
cwd: '/tmp/project-c',
|
||||||
|
userText: `来自「${duplicateSourceConversationTitle}」对话(ID: ${duplicateSourceConversationId})的消息:\n\n新候选`,
|
||||||
|
answerText: 'duplicate import answer b',
|
||||||
|
source: 'vscode',
|
||||||
|
fileStamp: '2026-03-12T00-00-21',
|
||||||
|
});
|
||||||
|
const codexAppObjectSourceFixture = createFakeCodexHistory(homeDir, {
|
||||||
|
threadId: 'codexapp-object-source-thread',
|
||||||
|
cwd: '/tmp/project-c',
|
||||||
|
userText: 'Object source import prompt',
|
||||||
|
answerText: 'Object source import answer',
|
||||||
|
source: { subagent: { thread_spawn: { parent_thread_id: 'parent-thread', depth: 1 } } },
|
||||||
|
fileStamp: '2026-03-12T00-00-22',
|
||||||
|
});
|
||||||
|
|
||||||
const port = await getFreePort();
|
const port = await getFreePort();
|
||||||
const password = 'Regression!234';
|
const password = 'Regression!234';
|
||||||
@@ -1899,6 +1936,11 @@ async function main() {
|
|||||||
assert(codexAppImportItem, 'Codex App session listing failed');
|
assert(codexAppImportItem, 'Codex App session listing failed');
|
||||||
assert(codexAppImportItem.agent === 'codexapp', 'Codex App import listing should echo target agent');
|
assert(codexAppImportItem.agent === 'codexapp', 'Codex App import listing should echo target agent');
|
||||||
assert(codexAppImportItem.alreadyImported === false, 'Codex App import should not reuse old Codex imported state');
|
assert(codexAppImportItem.alreadyImported === false, 'Codex App import should not reuse old Codex imported state');
|
||||||
|
const duplicateSourceItems = codexAppImportSessions.sessions.filter((item) => item.sourceConversationId === duplicateSourceConversationId);
|
||||||
|
assert(duplicateSourceItems.length === 1, 'Codex App import list should collapse rollout entries from the same cc-web source conversation');
|
||||||
|
assert(duplicateSourceItems[0].duplicateCount === 2, 'Collapsed Codex App import item should report duplicate rollout count');
|
||||||
|
const objectSourceItem = codexAppImportSessions.sessions.find((item) => item.threadId === codexAppObjectSourceFixture.threadId);
|
||||||
|
assert(objectSourceItem?.source === 'subagent', 'Codex App import list should format object source metadata');
|
||||||
|
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
type: 'import_codex_session',
|
type: 'import_codex_session',
|
||||||
|
|||||||
67
server.js
67
server.js
@@ -9882,10 +9882,46 @@ function resolveCodexImportAgent(value) {
|
|||||||
return value === 'codexapp' ? 'codexapp' : 'codex';
|
return value === 'codexapp' ? 'codexapp' : 'codex';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function codexImportSourceLabel(source) {
|
||||||
|
if (!source) return '';
|
||||||
|
if (typeof source === 'string') return source;
|
||||||
|
if (source && typeof source === 'object') {
|
||||||
|
if (source.subagent?.thread_spawn) return 'subagent';
|
||||||
|
if (source.type) return String(source.type);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractCcwebSourceConversation(title) {
|
||||||
|
const match = String(title || '').match(/^来自「([^」]+)」对话(ID:\s*([0-9a-fA-F-]{36}))的消息:/);
|
||||||
|
if (!match) return null;
|
||||||
|
return {
|
||||||
|
id: match[2].toLowerCase(),
|
||||||
|
title: match[1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function codexImportDedupeKey(item) {
|
||||||
|
if (item.sourceConversationId) return `ccweb-source:${item.sourceConversationId}`;
|
||||||
|
return `thread:${item.threadId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function codexImportItemTime(item) {
|
||||||
|
const time = new Date(item?.updatedAt || 0).getTime();
|
||||||
|
return Number.isFinite(time) ? time : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function preferCodexImportItem(next, current) {
|
||||||
|
const nextTime = codexImportItemTime(next);
|
||||||
|
const currentTime = codexImportItemTime(current);
|
||||||
|
if (nextTime !== currentTime) return nextTime > currentTime ? next : current;
|
||||||
|
return String(next.rolloutPath || '') > String(current.rolloutPath || '') ? next : current;
|
||||||
|
}
|
||||||
|
|
||||||
function handleListCodexSessions(ws, msg = {}) {
|
function handleListCodexSessions(ws, msg = {}) {
|
||||||
const importAgent = resolveCodexImportAgent(msg?.agent);
|
const importAgent = resolveCodexImportAgent(msg?.agent);
|
||||||
const imported = getImportedCodexThreadIds(importAgent);
|
const imported = getImportedCodexThreadIds(importAgent);
|
||||||
const items = [];
|
const itemsByKey = new Map();
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
for (const filePath of getCodexRolloutFiles()) {
|
for (const filePath of getCodexRolloutFiles()) {
|
||||||
const parsed = parseCodexRolloutFile(filePath);
|
const parsed = parseCodexRolloutFile(filePath);
|
||||||
@@ -9893,18 +9929,41 @@ function handleListCodexSessions(ws, msg = {}) {
|
|||||||
if (seen.has(parsed.meta.threadId)) continue;
|
if (seen.has(parsed.meta.threadId)) continue;
|
||||||
seen.add(parsed.meta.threadId);
|
seen.add(parsed.meta.threadId);
|
||||||
const title = parsed.meta.title || parsed.meta.threadId.slice(0, 20);
|
const title = parsed.meta.title || parsed.meta.threadId.slice(0, 20);
|
||||||
items.push({
|
const sourceConversation = parsed.meta.sourceConversationId
|
||||||
|
? {
|
||||||
|
id: parsed.meta.sourceConversationId,
|
||||||
|
title: parsed.meta.sourceConversationTitle || '',
|
||||||
|
}
|
||||||
|
: extractCcwebSourceConversation(title);
|
||||||
|
const item = {
|
||||||
threadId: parsed.meta.threadId,
|
threadId: parsed.meta.threadId,
|
||||||
title,
|
title,
|
||||||
cwd: parsed.meta.cwd || null,
|
cwd: parsed.meta.cwd || null,
|
||||||
updatedAt: parsed.meta.updatedAt || null,
|
updatedAt: parsed.meta.updatedAt || null,
|
||||||
cliVersion: parsed.meta.cliVersion || '',
|
cliVersion: parsed.meta.cliVersion || '',
|
||||||
source: parsed.meta.source || '',
|
source: codexImportSourceLabel(parsed.meta.source),
|
||||||
|
sourceConversationId: sourceConversation?.id || null,
|
||||||
|
sourceConversationTitle: sourceConversation?.title || '',
|
||||||
|
duplicateCount: 1,
|
||||||
rolloutPath: filePath,
|
rolloutPath: filePath,
|
||||||
agent: importAgent,
|
agent: importAgent,
|
||||||
alreadyImported: imported.has(parsed.meta.threadId),
|
alreadyImported: imported.has(parsed.meta.threadId),
|
||||||
});
|
};
|
||||||
|
const dedupeKey = codexImportDedupeKey(item);
|
||||||
|
const current = itemsByKey.get(dedupeKey);
|
||||||
|
if (!current) {
|
||||||
|
itemsByKey.set(dedupeKey, item);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const preferred = preferCodexImportItem(item, current);
|
||||||
|
preferred.duplicateCount = (current.duplicateCount || 1) + 1;
|
||||||
|
itemsByKey.set(dedupeKey, preferred);
|
||||||
}
|
}
|
||||||
|
const items = Array.from(itemsByKey.values()).sort((a, b) => {
|
||||||
|
const timeDiff = codexImportItemTime(b) - codexImportItemTime(a);
|
||||||
|
if (timeDiff) return timeDiff;
|
||||||
|
return String(b.rolloutPath || '').localeCompare(String(a.rolloutPath || ''));
|
||||||
|
});
|
||||||
wsSend(ws, { type: 'codex_sessions', sessions: items });
|
wsSend(ws, { type: 'codex_sessions', sessions: items });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user