diff --git a/public/app.js b/public/app.js
index da31d83..cfaef13 100644
--- a/public/app.js
+++ b/public/app.js
@@ -67,7 +67,7 @@
const SESSION_CACHE_MAX_WEIGHT = 1_500_000;
const SIDEBAR_SWIPE_TRIGGER = 72;
const SIDEBAR_SWIPE_MAX_VERTICAL_DRIFT = 42;
- const OLD_SESSION_COLLAPSE_VISIBLE_LIMIT = 5;
+ const OLD_SESSION_GROUP_INITIAL_VISIBLE = 3;
const OLD_SESSION_COLLAPSE_DAYS = 7;
const OLD_SESSION_COLLAPSE_MS = OLD_SESSION_COLLAPSE_DAYS * 24 * 60 * 60 * 1000;
@@ -194,6 +194,7 @@
let codexAppApprovalModal = null;
let pendingNewSessionRequest = null;
let pendingSessionSwitchRequest = null;
+ let sessionSwitchRequestSeq = 0;
let skipDeleteConfirm = localStorage.getItem('cc-web-skip-delete-confirm') === '1';
let pendingInitialSessionLoad = false;
let initialSessionListHandled = false;
@@ -1630,6 +1631,44 @@
}
}
+ function mergeSessionListSnapshot(snapshot) {
+ if (!snapshot?.sessionId) return;
+ const nextMeta = {
+ id: snapshot.sessionId,
+ sessionId: snapshot.sessionId,
+ cwd: snapshot.cwd || '',
+ projectName: snapshot.cwd ? getPathLeaf(snapshot.cwd) : snapshot.projectName || '',
+ title: snapshot.title || '新会话',
+ agent: normalizeAgent(snapshot.agent),
+ updated: snapshot.updated || new Date().toISOString(),
+ pinnedAt: snapshot.pinnedAt || null,
+ hasUnread: !!snapshot.hasUnread,
+ isRunning: !!snapshot.isRunning,
+ waitingOnChildren: !!snapshot.waitingOnChildren,
+ pendingReplyCount: Number(snapshot.pendingReplyCount || 0),
+ readyReplyCount: Number(snapshot.readyReplyCount || 0),
+ waitingReplyCount: Number(snapshot.waitingReplyCount || 0),
+ failedReplyCount: Number(snapshot.failedReplyCount || 0),
+ oversized: !!snapshot.oversized,
+ fileBytes: Number(snapshot.fileBytes || 0),
+ };
+ let found = false;
+ sessions = sessions.map((session) => {
+ if (session.id !== snapshot.sessionId) return session;
+ found = true;
+ return {
+ ...session,
+ ...nextMeta,
+ cwd: nextMeta.cwd || session.cwd || '',
+ projectName: nextMeta.cwd ? getPathLeaf(nextMeta.cwd) : session.projectName || nextMeta.projectName || '',
+ title: nextMeta.title || session.title,
+ };
+ });
+ if (!found) {
+ sessions = [nextMeta, ...sessions].sort(compareSessionUpdatedDesc);
+ }
+ }
+
function getSessionCacheDisposition(sessionId) {
const entry = sessionCache.get(sessionId);
const meta = getSessionMeta(sessionId);
@@ -3009,30 +3048,36 @@
return `${normalizeAgent(currentAgent)}:ungrouped`;
}
- function splitCollapsedOldSessions(regularSessions, pinnedCount, getCollapseKey = () => '') {
- const totalCount = pinnedCount + regularSessions.length;
- if (totalCount <= OLD_SESSION_COLLAPSE_VISIBLE_LIMIT) {
- return { visibleRegularSessions: regularSessions, hiddenOldSessions: [] };
- }
+ function expandOldSessionGroup(collapseKey) {
+ if (!collapseKey) return;
+ expandedOldSessionGroups.add(collapseKey);
+ }
+ function shouldAlwaysShowOldSession(session) {
+ return session.id === currentSessionId || session.isRunning || session.hasUnread || session.waitingOnChildren;
+ }
+
+ function splitCollapsedOldSessions(sessionItems, collapseKey) {
+ const isExpanded = collapseKey && expandedOldSessionGroups.has(collapseKey);
const nowMs = Date.now();
- const visibleRegularLimit = Math.max(0, OLD_SESSION_COLLAPSE_VISIBLE_LIMIT - pinnedCount);
- const visibleRegularSessions = [];
+ const visibleSessions = [];
const hiddenOldSessions = [];
- regularSessions.forEach((session, index) => {
- const collapseKey = getCollapseKey(session);
- const isExpanded = collapseKey && expandedOldSessionGroups.has(collapseKey);
- const shouldKeepVisible = session.id === currentSessionId || session.isRunning || session.hasUnread || session.waitingOnChildren;
- const canCollapse = !isExpanded && index >= visibleRegularLimit && isOlderThanOldSessionWindow(session, nowMs);
- if (canCollapse && !shouldKeepVisible) {
+ sessionItems.forEach((session) => {
+ const shouldHideOldSession = (
+ !isExpanded
+ && !shouldAlwaysShowOldSession(session)
+ && isOlderThanOldSessionWindow(session, nowMs)
+ && visibleSessions.length >= OLD_SESSION_GROUP_INITIAL_VISIBLE
+ );
+ if (shouldHideOldSession) {
hiddenOldSessions.push(session);
} else {
- visibleRegularSessions.push(session);
+ visibleSessions.push(session);
}
});
- return { visibleRegularSessions, hiddenOldSessions };
+ return { visibleSessions, hiddenOldSessions };
}
function createOldSessionLoadMoreButton(hiddenCount, collapseKey, projectName = '') {
@@ -3042,10 +3087,10 @@
button.setAttribute('aria-label', `加载更多${projectName ? ` ${projectName}` : ''} ${hiddenCount} 条 7 天前会话`);
button.innerHTML = `
加载更多
- ${hiddenCount} 条 7 天前会话
+ 还有 ${hiddenCount} 条
`;
button.addEventListener('click', () => {
- if (collapseKey) expandedOldSessionGroups.add(collapseKey);
+ expandOldSessionGroup(collapseKey);
renderSessionList();
});
return button;
@@ -3393,7 +3438,8 @@
function setSessionLoading(sessionId, options = {}) {
const loading = !!sessionId;
const blocking = options.blocking !== false;
- activeSessionLoad = loading ? { sessionId, blocking, snapshot: null } : null;
+ const requestId = loading ? (options.requestId || createSessionSwitchRequestId(sessionId)) : '';
+ activeSessionLoad = loading ? { sessionId, blocking, snapshot: null, requestId } : null;
const showOverlay = !!(loading && blocking);
document.body.classList.toggle('session-loading-active', showOverlay);
sessionLoadingOverlay.hidden = !showOverlay;
@@ -3413,6 +3459,11 @@
setSessionLoading(null, { blocking: false });
}
+ function createSessionSwitchRequestId(sessionId) {
+ sessionSwitchRequestSeq += 1;
+ return `session-load-${Date.now()}-${sessionSwitchRequestSeq}-${String(sessionId || '').slice(0, 8)}`;
+ }
+
function isBlockingSessionLoad(sessionId) {
return !!(activeSessionLoad &&
activeSessionLoad.blocking &&
@@ -3454,6 +3505,7 @@
sessionId,
blocking: options.blocking !== false,
label: options.label || '',
+ requestId: options.requestId || activeSessionLoad?.requestId || createSessionSwitchRequestId(sessionId),
};
if (ws && ws.readyState === 1 && wsAuthenticated) {
flushPendingSessionSwitch();
@@ -3471,9 +3523,10 @@
setSessionLoading(request.sessionId, {
blocking: request.blocking,
label: request.label || undefined,
+ requestId: request.requestId,
});
}
- ws.send(JSON.stringify({ type: 'load_session', sessionId: request.sessionId }));
+ ws.send(JSON.stringify({ type: 'load_session', sessionId: request.sessionId, requestId: request.requestId }));
}
function showCachedSession(sessionId) {
@@ -3971,6 +4024,7 @@
sessionId: activeSessionLoad.sessionId,
blocking: activeSessionLoad.blocking,
label: sessionLoadingLabel?.textContent || '',
+ requestId: activeSessionLoad.requestId || createSessionSwitchRequestId(activeSessionLoad.sessionId),
};
}
clearSessionLoading();
@@ -4068,28 +4122,29 @@
break;
case 'session_info':
- if (pendingNewSessionRequest) pendingNewSessionRequest = null;
const snapshot = normalizeSessionSnapshot(msg);
- sessions = sessions.map((session) => (
- session.id === snapshot.sessionId
- ? {
- ...session,
- cwd: snapshot.cwd || session.cwd || '',
- projectName: snapshot.cwd ? getPathLeaf(snapshot.cwd) : session.projectName || '',
- title: snapshot.title || session.title,
- pinnedAt: snapshot.pinnedAt || session.pinnedAt || null,
- isRunning: snapshot.isRunning,
- waitingOnChildren: snapshot.waitingOnChildren,
- pendingReplyCount: snapshot.pendingReplyCount,
- readyReplyCount: snapshot.readyReplyCount,
- waitingReplyCount: snapshot.waitingReplyCount,
- failedReplyCount: snapshot.failedReplyCount,
- }
- : session
- ));
- if (activeSessionLoad?.sessionId === msg.sessionId) {
+ const activeLoad = activeSessionLoad;
+ const pendingNewSession = pendingNewSessionRequest;
+ const messageRequestId = String(msg.requestId || '');
+ const matchesActiveLoad = !!(activeLoad?.sessionId === msg.sessionId
+ && (!activeLoad.requestId || activeLoad.requestId === messageRequestId));
+ const matchesPendingNewSession = !!(pendingNewSession
+ && (!pendingNewSession.requestId || pendingNewSession.requestId === messageRequestId));
+ const canSwitchToSessionInfo = matchesActiveLoad
+ || matchesPendingNewSession
+ || msg.sessionId === currentSessionId
+ || (!currentSessionId && !activeLoad && !pendingNewSession)
+ || (!messageRequestId && !activeLoad && !pendingNewSession);
+ mergeSessionListSnapshot(snapshot);
+ if (matchesActiveLoad) {
activeSessionLoad.snapshot = snapshot;
}
+ if (!canSwitchToSessionInfo) {
+ if (!msg.historyPending) cacheSessionSnapshot(snapshot);
+ renderSessionList();
+ break;
+ }
+ if (matchesPendingNewSession) pendingNewSessionRequest = null;
applySessionSnapshot(snapshot, {
immediate: isBlockingSessionLoad(msg.sessionId),
suppressUnreadToast: false,
@@ -4099,7 +4154,7 @@
setCurrentSessionRunningState(!!msg.isRunning);
}
if (!msg.historyPending) {
- if (activeSessionLoad?.sessionId === msg.sessionId) {
+ if (matchesActiveLoad) {
finalizeLoadedSession(msg.sessionId);
} else {
cacheSessionSnapshot(snapshot);
@@ -4380,6 +4435,7 @@
agent: request.agent,
mode: request.mode,
createCwd: true,
+ requestId: request.requestId,
});
},
onCancel: () => {
@@ -6515,33 +6571,6 @@
const { pinnedSessions, regularSessions } = splitPinnedSessions(visibleSessions);
const { groups: projectGroups, ungroupedSessions } = groupSessionsByProject(regularSessions);
- const oldSessionCollapseKeysBySessionId = new Map();
- projectGroups.forEach((group) => {
- const oldSessionCollapseKey = getProjectOldSessionCollapseKey(group);
- group.sessions.forEach((session) => {
- oldSessionCollapseKeysBySessionId.set(session.id, oldSessionCollapseKey);
- });
- });
- ungroupedSessions.forEach((session) => {
- oldSessionCollapseKeysBySessionId.set(session.id, getUngroupedOldSessionCollapseKey());
- });
- const { visibleRegularSessions, hiddenOldSessions } = isSearchingSessions
- ? { visibleRegularSessions: regularSessions, hiddenOldSessions: [] }
- : splitCollapsedOldSessions(
- regularSessions,
- pinnedSessions.length,
- (session) => oldSessionCollapseKeysBySessionId.get(session.id) || ''
- );
- const hiddenOldSessionCountsByKey = new Map();
- hiddenOldSessions.forEach((session) => {
- const oldSessionCollapseKey = oldSessionCollapseKeysBySessionId.get(session.id);
- if (!oldSessionCollapseKey) return;
- hiddenOldSessionCountsByKey.set(
- oldSessionCollapseKey,
- (hiddenOldSessionCountsByKey.get(oldSessionCollapseKey) || 0) + 1
- );
- });
- const visibleRegularSessionIds = new Set(visibleRegularSessions.map((session) => session.id));
if (pinnedSessions.length > 0) {
const pinnedGroupEl = document.createElement('section');
pinnedGroupEl.className = 'session-project-group session-pinned-group';
@@ -6563,8 +6592,9 @@
projectGroups.forEach((group, groupIndex) => {
const groupKey = getProjectCollapseKey(group);
const oldSessionCollapseKey = getProjectOldSessionCollapseKey(group);
- const visibleGroupSessions = group.sessions.filter((session) => visibleRegularSessionIds.has(session.id));
- const hiddenGroupOldSessionCount = hiddenOldSessionCountsByKey.get(oldSessionCollapseKey) || 0;
+ const { visibleSessions: visibleGroupSessions, hiddenOldSessions: hiddenGroupOldSessions } = isSearchingSessions
+ ? { visibleSessions: group.sessions, hiddenOldSessions: [] }
+ : splitCollapsedOldSessions(group.sessions, oldSessionCollapseKey);
const isCollapsed = !isSearchingSessions && collapsedProjectKeys.has(groupKey);
const hasActiveSession = group.sessions.some((session) => session.id === currentSessionId);
const hasUnreadSession = group.sessions.some((session) => session.hasUnread);
@@ -6596,8 +6626,8 @@
for (const s of visibleGroupSessions) {
groupBody.appendChild(createSessionListItem(s));
}
- if (hiddenGroupOldSessionCount > 0) {
- groupBody.appendChild(createOldSessionLoadMoreButton(hiddenGroupOldSessionCount, oldSessionCollapseKey, group.name));
+ if (hiddenGroupOldSessions.length > 0) {
+ groupBody.appendChild(createOldSessionLoadMoreButton(hiddenGroupOldSessions.length, oldSessionCollapseKey, group.name));
}
groupEl.appendChild(groupBody);
@@ -6614,15 +6644,16 @@
});
const ungroupedCollapseKey = getUngroupedOldSessionCollapseKey();
- const visibleUngroupedSessions = ungroupedSessions.filter((session) => visibleRegularSessionIds.has(session.id));
- const hiddenUngroupedOldSessionCount = hiddenOldSessionCountsByKey.get(ungroupedCollapseKey) || 0;
+ const { visibleSessions: visibleUngroupedSessions, hiddenOldSessions: hiddenUngroupedOldSessions } = isSearchingSessions
+ ? { visibleSessions: ungroupedSessions, hiddenOldSessions: [] }
+ : splitCollapsedOldSessions(ungroupedSessions, ungroupedCollapseKey);
for (const s of visibleUngroupedSessions) {
sessionList.appendChild(createSessionListItem(s));
}
- if (hiddenUngroupedOldSessionCount > 0) {
- sessionList.appendChild(createOldSessionLoadMoreButton(hiddenUngroupedOldSessionCount, ungroupedCollapseKey));
+ if (hiddenUngroupedOldSessions.length > 0) {
+ sessionList.appendChild(createOldSessionLoadMoreButton(hiddenUngroupedOldSessions.length, ungroupedCollapseKey));
}
}
@@ -8583,14 +8614,16 @@
const rawCwd = options.rawCwd !== undefined ? options.rawCwd : (cwd || '');
const agent = normalizeAgent(options.agent || currentAgent);
const mode = ['default', 'plan', 'yolo'].includes(options.mode) ? options.mode : currentMode;
+ const requestId = createSessionSwitchRequestId('new');
pendingNewSessionRequest = {
cwd,
rawCwd,
agent,
mode,
+ requestId,
};
if (cwd) saveRecentCwd(cwd);
- send({ type: 'new_session', cwd, agent, mode });
+ send({ type: 'new_session', cwd, agent, mode, requestId });
}
// --- New Session Modal ---
diff --git a/public/style.css b/public/style.css
index 661ddb7..90d4523 100644
--- a/public/style.css
+++ b/public/style.css
@@ -200,8 +200,8 @@ html[data-theme='coolvibe'] .session-search-clear:focus-visible {
html[data-theme='coolvibe'] .session-project-header {
background: linear-gradient(180deg, rgba(247, 251, 252, 0.94), rgba(239, 248, 250, 0.88));
- color: #5f7f87;
- border-bottom: 1px solid rgba(191, 220, 228, 0.56);
+ border-color: rgba(156, 199, 211, 0.9);
+ color: #335e69;
}
html[data-theme='coolvibe'] .session-project-count {
@@ -337,8 +337,8 @@ html[data-theme='coolvibe'] .theme-card.active {
html[data-theme='editorial'] .session-project-header {
background: linear-gradient(180deg, rgba(239, 232, 220, 0.94), rgba(246, 241, 232, 0.84));
- color: #7f6f61;
- border-bottom: 1px solid rgba(139, 94, 60, 0.12);
+ border-color: rgba(139, 94, 60, 0.22);
+ color: #4f4035;
}
html[data-theme='editorial'] .session-project-count {
@@ -1146,15 +1146,18 @@ body.session-loading-active {
align-items: center;
justify-content: space-between;
gap: 8px;
- margin: 4px 2px 5px;
- padding: 6px 8px 5px;
- background: rgba(242, 235, 226, 0.92);
+ margin: 6px 3px 6px;
+ padding: 6px 8px;
+ border: 1px solid rgba(134, 106, 80, 0.22);
+ border-radius: 8px;
+ background: rgba(255, 249, 242, 0.88);
backdrop-filter: blur(8px);
- color: var(--text-muted);
- font-size: 11px;
+ box-shadow: 0 1px 0 rgba(255, 255, 255, 0.72), 0 1px 6px rgba(45, 31, 20, 0.04);
+ color: var(--text-secondary);
+ font-size: 12px;
font-weight: 800;
- letter-spacing: 0.08em;
- text-transform: uppercase;
+ letter-spacing: 0;
+ text-transform: none;
}
.session-project-name {
min-width: 0;
@@ -1162,14 +1165,15 @@ body.session-loading-active {
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
+ color: var(--text-primary);
}
.session-project-toggle {
min-width: 0;
flex: 1;
display: inline-flex;
align-items: center;
- gap: 5px;
- height: 24px;
+ gap: 6px;
+ height: 26px;
padding: 0;
border: 0;
background: transparent;
@@ -1182,9 +1186,13 @@ body.session-loading-active {
}
.session-project-toggle:hover,
.session-project-toggle:focus-visible {
- color: var(--text-secondary);
+ color: var(--accent);
outline: none;
}
+.session-project-toggle:hover .session-project-name,
+.session-project-toggle:focus-visible .session-project-name {
+ color: var(--accent);
+}
.session-project-toggle:focus-visible .session-project-name {
text-decoration: underline;
text-underline-offset: 3px;
@@ -1192,8 +1200,8 @@ body.session-loading-active {
.session-project-chevron {
width: 12px;
flex-shrink: 0;
- color: var(--text-muted);
- font-size: 10px;
+ color: currentColor;
+ font-size: 11px;
line-height: 1;
text-align: center;
transform: translateY(-0.5px);
@@ -1211,9 +1219,10 @@ body.session-loading-active {
min-width: 22px;
height: 18px;
padding: 0 7px;
+ border: 1px solid rgba(134, 106, 80, 0.14);
border-radius: 999px;
- background: var(--bg-tertiary);
- color: var(--text-secondary);
+ background: rgba(45, 31, 20, 0.06);
+ color: var(--text-primary);
font-size: 10px;
letter-spacing: 0;
}
@@ -1244,6 +1253,9 @@ body.session-loading-active {
.session-pinned-header {
color: var(--accent);
}
+.session-pinned-header .session-project-name {
+ color: var(--accent);
+}
.session-project-sessions[hidden] {
display: none;
}
@@ -1256,6 +1268,9 @@ body.session-loading-active {
.session-project-group.has-active-session.collapsed .session-project-header {
color: var(--accent);
}
+.session-project-group.has-active-session.collapsed .session-project-name {
+ color: var(--accent);
+}
.session-project-group.has-active-session.collapsed .session-project-count {
background: var(--accent-light);
color: var(--accent);
@@ -1460,23 +1475,24 @@ body.session-loading-active {
color: var(--danger);
}
.session-list-load-more {
- width: calc(100% - 4px);
- margin: 6px 2px 10px;
- padding: 9px 10px;
- border: 1px solid var(--border-color);
- border-radius: 8px;
- background: rgba(255, 249, 242, 0.72);
+ width: fit-content;
+ max-width: calc(100% - 18px);
+ margin: 2px 8px 6px;
+ padding: 3px 7px;
+ border: 1px solid rgba(134, 106, 80, 0.18);
+ border-radius: 6px;
+ background: transparent;
color: var(--text-secondary);
cursor: pointer;
- display: flex;
+ display: inline-flex;
align-items: center;
- justify-content: space-between;
- gap: 10px;
+ justify-content: flex-start;
+ gap: 6px;
text-align: left;
transition: background 0.16s, border-color 0.16s, color 0.16s, transform 0.16s;
}
.session-list-load-more:hover {
- background: var(--bg-tertiary);
+ background: rgba(255, 249, 242, 0.58);
border-color: rgba(192, 85, 58, 0.24);
color: var(--accent);
transform: translateY(-1px);
@@ -1487,13 +1503,13 @@ body.session-loading-active {
}
.session-list-load-more-title {
min-width: 0;
- font-size: 13px;
+ font-size: 12px;
font-weight: 700;
}
.session-list-load-more-meta {
flex-shrink: 0;
color: var(--text-muted);
- font-size: 11px;
+ font-size: 10px;
}
/* Inline edit in sidebar */
.session-item-edit-input {
diff --git a/scripts/regression.js b/scripts/regression.js
index 4ec4acc..4c5e71f 100644
--- a/scripts/regression.js
+++ b/scripts/regression.js
@@ -633,8 +633,9 @@ async function main() {
mkdirp(path.join(pickerRoot, 'beta'));
fs.writeFileSync(path.join(pickerRoot, 'note.txt'), 'not a directory');
- ws.send(JSON.stringify({ type: 'new_session', agent: 'codex', mode: 'plan' }));
+ ws.send(JSON.stringify({ type: 'new_session', agent: 'codex', mode: 'plan', requestId: 'reg-new-default' }));
const defaultCodexSession = await nextMessage(messages, ws, (msg) => msg.type === 'session_info' && msg.agent === 'codex' && msg.title === 'New Chat');
+ assert(defaultCodexSession.requestId === 'reg-new-default', 'new_session session_info should echo requestId');
assert(defaultCodexSession.cwd === homeDir, 'Codex new_session without cwd should default to HOME');
const missingCwd = path.join(tempRoot, 'missing-space', 'nested-project');
@@ -1081,8 +1082,9 @@ async function main() {
assert(returnedPendingDetail.status === 200 && returnedPendingDetail.body?.ok, 'Returned pending reply detail should remain queryable from source history');
assert(returnedPendingDetail.body.status === 'returned' && returnedPendingDetail.body.returned === true, 'Returned pending reply detail should report returned status');
- ws.send(JSON.stringify({ type: 'load_session', sessionId: busySourceSession.sessionId }));
+ ws.send(JSON.stringify({ type: 'load_session', sessionId: busySourceSession.sessionId, requestId: 'reg-load-busy-source' }));
const loadedBusySource = await nextMessage(messages, ws, (msg) => msg.type === 'session_info' && msg.sessionId === busySourceSession.sessionId);
+ assert(loadedBusySource.requestId === 'reg-load-busy-source', 'load_session session_info should echo requestId');
assert(loadedBusySource.isRunning === false, 'Busy source should be idle after background run completed');
assert(loadedBusySource.waitingOnChildren === false && loadedBusySource.pendingReplyCount === 0, 'Busy source should clear waiting state after queued reply is flushed');
diff --git a/server.js b/server.js
index 2f1337a..0b3ee4e 100644
--- a/server.js
+++ b/server.js
@@ -5038,7 +5038,7 @@ wss.on('connection', (ws, req) => {
handleNewSession(ws, msg);
break;
case 'load_session':
- handleLoadSession(ws, msg.sessionId);
+ handleLoadSession(ws, msg);
break;
case 'load_history_page':
handleLoadHistoryPage(ws, msg);
@@ -5602,7 +5602,7 @@ function handleSlashCommand(ws, text, sessionId, fallbackAgent) {
clearRuntimeSessionId(session);
session.updated = new Date().toISOString();
saveSession(session);
- wsSend(ws, {
+ wsSend(ws, attachClientRequestId({
type: 'session_info',
sessionId: session.id,
messages: [],
@@ -5614,7 +5614,7 @@ function handleSlashCommand(ws, text, sessionId, fallbackAgent) {
cwd: session.cwd || null,
totalCost: session.totalCost || 0,
totalUsage: session.totalUsage || null,
- });
+ }, { sessionId }));
}
wsSend(ws, { type: 'system_message', message: '会话已清除,上下文已重置。' });
break;
@@ -5885,6 +5885,11 @@ function buildSessionInfoPayload(session) {
};
}
+function attachClientRequestId(payload, source = {}) {
+ const requestId = String(source?.requestId || '').trim();
+ return requestId ? { ...payload, requestId } : payload;
+}
+
function handleNewSession(ws, msg) {
const result = createPersistentConversationSession(msg || {}, {
defaultAgent: normalizeAgent(msg?.agent),
@@ -5902,7 +5907,7 @@ function handleNewSession(ws, msg) {
const { session } = result;
detachWsFromActiveRuntimes(ws);
wsSessionMap.set(ws, session.id);
- wsSend(ws, buildSessionInfoPayload(session));
+ wsSend(ws, attachClientRequestId(buildSessionInfoPayload(session), msg));
sendSessionList(ws);
}
@@ -5929,7 +5934,8 @@ function handleLoadHistoryPage(ws, msg = {}) {
});
}
-function handleLoadSession(ws, sessionId) {
+function handleLoadSession(ws, msg) {
+ const sessionId = sanitizeId(typeof msg === 'string' ? msg : msg?.sessionId);
reconcilePendingCrossConversationReplies();
const session = loadSession(sessionId);
if (!session) {
@@ -5961,7 +5967,7 @@ function handleLoadSession(ws, sessionId) {
saveSession(refreshedSession);
}
- wsSend(ws, {
+ wsSend(ws, attachClientRequestId({
type: 'session_info',
sessionId: refreshedSession.id,
messages: recentMessages,
@@ -5987,7 +5993,7 @@ function handleLoadSession(ws, sessionId) {
waitingReplyCount: waitState.waitingReplyCount,
failedReplyCount: waitState.failedReplyCount,
pendingReplies: waitState.pendingReplies,
- });
+ }, msg));
if (olderChunks.length > 0) {
olderChunks.forEach((chunk, index) => {
@@ -8331,7 +8337,7 @@ function handleImportNativeSession(ws, msg) {
};
saveSession(session);
wsSessionMap.set(ws, id);
- wsSend(ws, {
+ wsSend(ws, attachClientRequestId({
type: 'session_info',
sessionId: id,
messages: session.messages,
@@ -8347,7 +8353,7 @@ function handleImportNativeSession(ws, msg) {
hasUnread: false,
historyPending: false,
isRunning: false,
- });
+ }, msg));
sendSessionList(ws);
}
@@ -8432,7 +8438,7 @@ function handleImportCodexSession(ws, msg) {
saveSession(session);
wsSessionMap.set(ws, id);
- wsSend(ws, {
+ wsSend(ws, attachClientRequestId({
type: 'session_info',
sessionId: id,
messages: session.messages,
@@ -8448,7 +8454,7 @@ function handleImportCodexSession(ws, msg) {
hasUnread: false,
historyPending: false,
isRunning: false,
- });
+ }, msg));
sendSessionList(ws);
}