Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions web/src/components/ChatView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
3 changes: 3 additions & 0 deletions web/src/components/MiniSession.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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)}
Expand Down
1 change: 1 addition & 0 deletions web/src/components/Sidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'}
Expand Down
6 changes: 4 additions & 2 deletions web/src/lib/desktopContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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, {
Expand Down Expand Up @@ -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'),
Expand Down
43 changes: 37 additions & 6 deletions web/src/lib/desktopContextMenu.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand All @@ -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', () => {
Expand Down Expand Up @@ -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',
Expand All @@ -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,
Expand Down
8 changes: 4 additions & 4 deletions web/src/lib/sessionExplorerArchitecture.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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\}/);
});
Loading