mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-23 08:45:50 +00:00
fix: listSessions 严格按 cwd 过滤并移除 session/load 过严校验
- listSessions: 客户端省略 cwd 时回退到 getOriginalCwd(),并对每个候选会话的 存储 cwd 做 canonicalizePath 规范化后与请求 cwd 严格匹配,确保只返回真正属 于当前工作区的会话(符合 session-list.mdx "Only sessions with a matching cwd are returned") - sessionLifecycle: 移除 getOrCreateSession 中审计 2.2 添加的 cwd 一致性校验, 它会拒绝 resolveSessionFilePath worktree fallback 找到的合法会话加载 - 补充 listSessions 的 5 个测试用例覆盖 cwd 透传/fallback/分页拒绝/无 cwd 过滤 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win>
This commit is contained in:
@@ -71,10 +71,13 @@ mockModulePreservingExports('../../../utils/config.ts', {
|
||||
|
||||
const mockSwitchSession = mock(() => {})
|
||||
|
||||
const mockGetOriginalCwd = mock(() => '/current/working/dir')
|
||||
mockModulePreservingExports('../../../bootstrap/state.ts', {
|
||||
setOriginalCwd: mock(() => {}),
|
||||
switchSession: mockSwitchSession,
|
||||
addSlowOperation: mock(() => {}),
|
||||
getOriginalCwd: mockGetOriginalCwd,
|
||||
getSessionProjectDir: mock(() => null),
|
||||
})
|
||||
|
||||
const mockGetDefaultAppState = mock(() => ({
|
||||
@@ -116,8 +119,9 @@ mockModulePreservingExports('../bridge.ts', {
|
||||
})),
|
||||
})
|
||||
|
||||
const mockListSessionsImpl = mock(async () => [])
|
||||
mockModulePreservingExports('../../../utils/listSessionsImpl.ts', {
|
||||
listSessionsImpl: mock(async () => []),
|
||||
listSessionsImpl: mockListSessionsImpl,
|
||||
})
|
||||
|
||||
const mockResolveSessionFilePath = mock(async () => ({
|
||||
@@ -241,6 +245,10 @@ describe('AcpAgent', () => {
|
||||
mockGetDefaultAppState.mockClear()
|
||||
mockGetSettings.mockReset()
|
||||
mockGetSettings.mockImplementation(() => ({}))
|
||||
mockListSessionsImpl.mockReset()
|
||||
mockListSessionsImpl.mockImplementation(async () => [])
|
||||
mockGetOriginalCwd.mockReset()
|
||||
mockGetOriginalCwd.mockImplementation(() => '/current/working/dir')
|
||||
;(forwardSessionUpdates as ReturnType<typeof mock>).mockReset()
|
||||
;(forwardSessionUpdates as ReturnType<typeof mock>).mockImplementation(
|
||||
async () => ({ stopReason: 'end_turn' as const }),
|
||||
@@ -1320,6 +1328,63 @@ describe('AcpAgent', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('listSessions', () => {
|
||||
test('passes params.cwd through to listSessionsImpl when provided', async () => {
|
||||
const agent = new AcpAgent(makeConn())
|
||||
await agent.listSessions({ cwd: '/explicit/path' } as any)
|
||||
expect(mockListSessionsImpl).toHaveBeenCalledWith({
|
||||
dir: '/explicit/path',
|
||||
})
|
||||
})
|
||||
|
||||
test('falls back to current working dir when client omits cwd', async () => {
|
||||
// Standard clients (Goose, possibly others) call session/list with
|
||||
// empty params. Without a fallback, listSessionsImpl treats undefined
|
||||
// dir as "all projects" and returns every session on disk.
|
||||
mockGetOriginalCwd.mockImplementation(() => '/active/project')
|
||||
const agent = new AcpAgent(makeConn())
|
||||
await agent.listSessions({} as any)
|
||||
expect(mockListSessionsImpl).toHaveBeenCalledWith({
|
||||
dir: '/active/project',
|
||||
})
|
||||
})
|
||||
|
||||
test('falls back to current working dir when client sends null cwd', async () => {
|
||||
mockGetOriginalCwd.mockImplementation(() => '/active/project')
|
||||
const agent = new AcpAgent(makeConn())
|
||||
await agent.listSessions({ cwd: null } as any)
|
||||
expect(mockListSessionsImpl).toHaveBeenCalledWith({
|
||||
dir: '/active/project',
|
||||
})
|
||||
})
|
||||
|
||||
test('rejects client-supplied cursor (pagination not implemented)', async () => {
|
||||
const agent = new AcpAgent(makeConn())
|
||||
await expect(
|
||||
agent.listSessions({ cursor: 'page2' } as any),
|
||||
).rejects.toThrow(/Pagination cursor not supported/)
|
||||
})
|
||||
|
||||
test('filters out candidates without a cwd field', async () => {
|
||||
mockListSessionsImpl.mockImplementation(
|
||||
async () =>
|
||||
[
|
||||
{
|
||||
sessionId: 'with-cwd',
|
||||
cwd: '/p',
|
||||
summary: 'Has cwd',
|
||||
lastModified: 0,
|
||||
},
|
||||
{ sessionId: 'no-cwd', summary: 'No cwd', lastModified: 0 },
|
||||
] as any,
|
||||
)
|
||||
const agent = new AcpAgent(makeConn())
|
||||
const res = await agent.listSessions({ cwd: '/p' } as any)
|
||||
expect(res.sessions).toHaveLength(1)
|
||||
expect(res.sessions[0].sessionId).toBe('with-cwd')
|
||||
})
|
||||
})
|
||||
|
||||
describe('sessionId alignment with global state', () => {
|
||||
test('newSession calls switchSession with the generated sessionId', async () => {
|
||||
const agent = new AcpAgent(makeConn())
|
||||
|
||||
Reference in New Issue
Block a user