From fc80f218886e30e4ef07edc7b391f80b462cd1c7 Mon Sep 17 00:00:00 2001 From: tmoonlight Date: Mon, 26 Oct 2026 23:09:11 +0800 Subject: [PATCH] Fix session folder action to open its workspace --- web/src/components/ChatView.jsx | 2 + web/src/components/MiniSession.jsx | 3 ++ web/src/components/Sidebar.jsx | 1 + web/src/lib/desktopContextMenu.js | 6 ++- web/src/lib/desktopContextMenu.test.js | 43 ++++++++++++++++--- .../lib/sessionExplorerArchitecture.test.js | 8 ++-- 6 files changed, 51 insertions(+), 12 deletions(-) diff --git a/web/src/components/ChatView.jsx b/web/src/components/ChatView.jsx index 4724519cb..54d022554 100644 --- a/web/src/components/ChatView.jsx +++ b/web/src/components/ChatView.jsx @@ -6240,6 +6240,8 @@ export function ChatView({ titleTarget, actionsTarget, children, sessionRef, ses type="button" data-desktop-session-id={readOnlyExternalSession ? undefined : sid || undefined} data-desktop-session-workspace={sessionWorkspaceHash || undefined} + data-desktop-session-workspace-path={sessionIsNoWorkspace ? undefined : ref?.cwd || undefined} + data-desktop-session-no-workspace={sessionIsNoWorkspace ? 'true' : undefined} data-desktop-session-path={sessionPath || undefined} data-desktop-session-pinned={sessionPinned ? 'true' : 'false'} data-desktop-session-title={title || undefined} diff --git a/web/src/components/MiniSession.jsx b/web/src/components/MiniSession.jsx index 002df3a7c..0fc9b8c91 100644 --- a/web/src/components/MiniSession.jsx +++ b/web/src/components/MiniSession.jsx @@ -154,6 +154,7 @@ export function MiniSession({ session, compact, onClick }) { const stateLabel = active ? 'running' : session.status; const sessionId = session.sessionId || session.id; const workspaceHash = session.workspaceHash || session.workspace_hash || ''; + const noWorkspace = !!(session.noWorkspace || session.no_workspace); const sessionPath = session.sessionPath || session.session_path || ''; const title = sessionDisplayTitle(session); @@ -176,6 +177,8 @@ export function MiniSession({ session, compact, onClick }) { type="button" data-desktop-session-id={sessionId || undefined} data-desktop-session-workspace={workspaceHash || undefined} + data-desktop-session-workspace-path={noWorkspace ? undefined : session.cwd || undefined} + data-desktop-session-no-workspace={noWorkspace ? 'true' : undefined} data-desktop-session-path={sessionPath || undefined} data-desktop-session-title={title || undefined} onClick={() => onClick?.(session)} diff --git a/web/src/components/Sidebar.jsx b/web/src/components/Sidebar.jsx index 051d5eaaa..6e1354d23 100644 --- a/web/src/components/Sidebar.jsx +++ b/web/src/components/Sidebar.jsx @@ -1094,6 +1094,7 @@ function SessionRow({ data-desktop-session-id={!isGroup ? s.id || undefined : undefined} data-sidebar-group-id={isGroup ? s.id : undefined} data-desktop-session-workspace={workspaceHash || undefined} + data-desktop-session-workspace-path={noWorkspace ? undefined : s.cwd || undefined} data-desktop-session-no-workspace={noWorkspace ? 'true' : undefined} data-desktop-session-path={sessionPath || undefined} data-desktop-session-pinned={pinned ? 'true' : 'false'} diff --git a/web/src/lib/desktopContextMenu.js b/web/src/lib/desktopContextMenu.js index 8006ec46d..1aef460db 100644 --- a/web/src/lib/desktopContextMenu.js +++ b/web/src/lib/desktopContextMenu.js @@ -381,6 +381,7 @@ export function buildDesktopContextMenuItems({ if (!editable) { if (sessionTarget) { + const workspacePath = sessionTarget.noWorkspace ? '' : sessionTarget.workspacePath || ''; addAction(items, sessionTarget.pinned ? DESKTOP_CONTEXT_ACTIONS.UNPIN_SESSION : DESKTOP_CONTEXT_ACTIONS.PIN_SESSION, sessionTarget); addAction(items, sessionTarget.unread ? DESKTOP_CONTEXT_ACTIONS.MARK_SESSION_READ : DESKTOP_CONTEXT_ACTIONS.MARK_SESSION_UNREAD, sessionTarget); addAction(items, DESKTOP_CONTEXT_ACTIONS.RENAME_SESSION, sessionTarget); @@ -390,8 +391,8 @@ export function buildDesktopContextMenuItems({ addAction( items, DESKTOP_CONTEXT_ACTIONS.OPEN_IN_EXPLORER, - { path: sessionTarget.sessionPath, kind: 'file' }, - { enabled: !!sessionTarget.sessionPath }, + { path: workspacePath, kind: 'workspace' }, + { enabled: !!workspacePath }, ); if (sessionTarget.canArchive) { addAction(items, DESKTOP_CONTEXT_ACTIONS.ARCHIVE_SESSION, sessionTarget, { @@ -592,6 +593,7 @@ export function sessionTargetFromElement(target) { type: 'session', sessionId, workspaceHash: getAttr(el, 'data-desktop-session-workspace', 'desktopSessionWorkspace'), + workspacePath: getAttr(el, 'data-desktop-session-workspace-path', 'desktopSessionWorkspacePath'), noWorkspace: boolAttr(el, 'data-desktop-session-no-workspace', 'desktopSessionNoWorkspace'), title: getAttr(el, 'data-desktop-session-title', 'desktopSessionTitle'), sessionPath: getAttr(el, 'data-desktop-session-path', 'desktopSessionPath'), diff --git a/web/src/lib/desktopContextMenu.test.js b/web/src/lib/desktopContextMenu.test.js index 1ab8eaf70..a3c4a76fd 100644 --- a/web/src/lib/desktopContextMenu.test.js +++ b/web/src/lib/desktopContextMenu.test.js @@ -233,6 +233,7 @@ test('未置顶会话目标显示会话动作', () => { sessionId: 's1', title: 'T', sessionPath: 'C:/Users/test/.acecode/projects/hash/s1.jsonl', + workspacePath: 'C:/repo', pinned: false, canArchive: true, }, @@ -249,16 +250,44 @@ test('未置顶会话目标显示会话动作', () => { ]); assert.deepEqual( items.find((item) => item.id === DESKTOP_CONTEXT_ACTIONS.OPEN_IN_EXPLORER).target, - { path: 'C:/Users/test/.acecode/projects/hash/s1.jsonl', kind: 'file' }, + { path: 'C:/repo', kind: 'workspace' }, ); }); -test('尚未落盘的会话保留禁用的资源管理器动作', () => { +test('尚未落盘的会话仍可打开所属工作区', () => { const explorer = buildDesktopContextMenuItems({ - sessionTarget: { sessionId: 's1', title: 'T', sessionPath: '' }, + sessionTarget: { sessionId: 's1', title: 'T', workspacePath: 'C:/repo', sessionPath: '' }, }).find((item) => item.id === DESKTOP_CONTEXT_ACTIONS.OPEN_IN_EXPLORER); - assert.equal(explorer.enabled, false); - assert.deepEqual(explorer.target, { path: '', kind: 'file' }); + assert.equal(explorer.enabled, true); + assert.deepEqual(explorer.target, { path: 'C:/repo', kind: 'workspace' }); +}); + +test('无工作区或缺失工作区路径不回退到 JSONL 存储目录', () => { + for (const scope of [{}, { workspacePath: '' }, { noWorkspace: true, workspacePath: 'C:/stale' }]) { + const explorer = buildDesktopContextMenuItems({ + sessionTarget: { + sessionId: 's1', + sessionPath: 'C:/Users/test/.acecode/projects/hash/s1.jsonl', + ...scope, + }, + }).find((item) => item.id === DESKTOP_CONTEXT_ACTIONS.OPEN_IN_EXPLORER); + assert.equal(explorer.enabled, false); + assert.deepEqual(explorer.target, { path: '', kind: 'workspace' }); + } +}); + +test('会话菜单只使用所属工作区,不使用 worktree 执行目录', () => { + const explorer = buildDesktopContextMenuItems({ + sessionTarget: { + sessionId: 's1', + workspacePath: 'N:/项目/原工作区', + working_cwd: 'N:/worktrees/task', + worktree: { path: 'N:/worktrees/task' }, + sessionPath: 'N:/Users/test/.acecode/projects/hash/s1.jsonl', + }, + }).find((item) => item.id === DESKTOP_CONTEXT_ACTIONS.OPEN_IN_EXPLORER); + assert.equal(explorer.enabled, true); + assert.deepEqual(explorer.target, { path: 'N:/项目/原工作区', kind: 'workspace' }); }); test('descriptor metadata includes label keys, disabled states, and scoped confirmations', () => { @@ -742,12 +771,13 @@ test('右键目标显式保留无工作区任务身份', () => { assert.equal(pin.target.workspaceHash, ''); }); -test('右键目标提取 session JSONL path', () => { +test('右键目标区分会话工作区目录与 JSONL 路径', () => { const element = elementFor(SESSION_PIN_TARGET_SELECTOR, { 'data-desktop-session-id': 's1', 'data-desktop-session-workspace': 'w1', 'data-desktop-session-title': 'Session 1', 'data-desktop-session-path': 'C:/Users/test/.acecode/projects/hash/s1.jsonl', + 'data-desktop-session-workspace-path': 'C:/repo', 'data-desktop-session-pinned': 'false', 'data-desktop-session-unread': 'true', 'data-desktop-session-archive': 'true', @@ -759,6 +789,7 @@ test('右键目标提取 session JSONL path', () => { noWorkspace: false, title: 'Session 1', sessionPath: 'C:/Users/test/.acecode/projects/hash/s1.jsonl', + workspacePath: 'C:/repo', pinned: false, unread: true, canArchive: true, diff --git a/web/src/lib/sessionExplorerArchitecture.test.js b/web/src/lib/sessionExplorerArchitecture.test.js index f74b2651b..b6c5b15cb 100644 --- a/web/src/lib/sessionExplorerArchitecture.test.js +++ b/web/src/lib/sessionExplorerArchitecture.test.js @@ -15,12 +15,12 @@ function test(name, fn) { } } -test('所有共享会话右键入口都携带规范 JSONL 路径', () => { +test('所有共享会话右键入口都携带所属工作区目录并排除无工作区会话', () => { const sidebar = source('../components/Sidebar.jsx'); const chatView = source('../components/ChatView.jsx'); const miniSession = source('../components/MiniSession.jsx'); - assert.match(sidebar, /data-desktop-session-path=\{sessionPath \|\| undefined\}/); - assert.match(chatView, /data-desktop-session-path=\{sessionPath \|\| undefined\}/); - assert.match(miniSession, /data-desktop-session-path=\{sessionPath \|\| undefined\}/); + assert.match(sidebar, /data-desktop-session-workspace-path=\{noWorkspace \? undefined : s\.cwd \|\| undefined\}/); + assert.match(chatView, /data-desktop-session-workspace-path=\{sessionIsNoWorkspace \? undefined : ref\?\.cwd \|\| undefined\}/); + assert.match(miniSession, /data-desktop-session-workspace-path=\{noWorkspace \? undefined : session\.cwd \|\| undefined\}/); });