Skip to content

Commit 3d7b32f

Browse files
Merge pull request #1117 from xuzhongpeng/fix/acp-session-id-alignment
fix(acp): 对齐 ACP session ID 与全局会话状态
2 parents ea51474 + 2c8a22d commit 3d7b32f

2 files changed

Lines changed: 78 additions & 1 deletion

File tree

src/services/acp/__tests__/agent.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,11 @@ mockModulePreservingExports('../../../utils/config.ts', {
6969
enableConfigs: mock(() => {}),
7070
})
7171

72+
const mockSwitchSession = mock(() => {})
73+
7274
mockModulePreservingExports('../../../bootstrap/state.ts', {
7375
setOriginalCwd: mock(() => {}),
76+
switchSession: mockSwitchSession,
7477
addSlowOperation: mock(() => {}),
7578
})
7679

@@ -222,6 +225,7 @@ describe('AcpAgent', () => {
222225
delete process.env.ACP_PERMISSION_MODE
223226
delete process.env.CLAUDE_CODE_ACP_ALLOW_BYPASS_PERMISSIONS
224227
mockSetModel.mockClear()
228+
mockSwitchSession.mockClear()
225229
mockSubmitMessage.mockReset()
226230
mockSubmitMessage.mockImplementation(async function* (_input: string) {})
227231
mockGetMainLoopModel.mockClear()
@@ -1157,4 +1161,66 @@ describe('AcpAgent', () => {
11571161
expect(commit.input).toEqual({ hint: '[message]' })
11581162
})
11591163
})
1164+
1165+
describe('sessionId alignment with global state', () => {
1166+
test('newSession calls switchSession with the generated sessionId', async () => {
1167+
const agent = new AcpAgent(makeConn())
1168+
const res = await agent.newSession({ cwd: '/tmp' } as any)
1169+
expect(mockSwitchSession).toHaveBeenCalledWith(res.sessionId)
1170+
})
1171+
1172+
test('resumeSession calls switchSession with the requested sessionId', async () => {
1173+
const agent = new AcpAgent(makeConn())
1174+
const requestedId = 'resume-test-session-id'
1175+
await agent.unstable_resumeSession({
1176+
sessionId: requestedId,
1177+
cwd: '/tmp',
1178+
mcpServers: [],
1179+
} as any)
1180+
1181+
expect(mockSwitchSession).toHaveBeenCalledWith(requestedId)
1182+
})
1183+
1184+
test('loadSession calls switchSession with the requested sessionId', async () => {
1185+
const agent = new AcpAgent(makeConn())
1186+
const requestedId = 'load-test-session-id'
1187+
await agent.loadSession({
1188+
sessionId: requestedId,
1189+
cwd: '/tmp',
1190+
mcpServers: [],
1191+
} as any)
1192+
1193+
expect(mockSwitchSession).toHaveBeenCalledWith(requestedId)
1194+
})
1195+
1196+
test('resumeSession with existing session still calls switchSession', async () => {
1197+
const agent = new AcpAgent(makeConn())
1198+
const { sessionId } = await agent.newSession({ cwd: '/tmp' } as any)
1199+
mockSwitchSession.mockClear()
1200+
1201+
// Resume the same session — should still align global state
1202+
await agent.unstable_resumeSession({
1203+
sessionId,
1204+
cwd: '/tmp',
1205+
mcpServers: [],
1206+
} as any)
1207+
1208+
expect(mockSwitchSession).toHaveBeenCalledWith(sessionId)
1209+
})
1210+
1211+
test('prompt does not trigger additional switchSession for multi-session', async () => {
1212+
const agent = new AcpAgent(makeConn())
1213+
await agent.newSession({ cwd: '/tmp' } as any)
1214+
await agent.newSession({ cwd: '/tmp' } as any)
1215+
mockSwitchSession.mockClear()
1216+
1217+
// Prompts should not call switchSession — alignment happens at session creation
1218+
const s1 = agent.sessions.keys().next().value
1219+
await agent.prompt({
1220+
sessionId: s1,
1221+
prompt: [{ type: 'text', text: 'hello' }],
1222+
} as any)
1223+
expect(mockSwitchSession).not.toHaveBeenCalled()
1224+
})
1225+
})
11601226
})

src/services/acp/agent.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ import { getEmptyToolPermissionContext } from '../../Tool.js'
5353
import type { PermissionMode } from '../../types/permissions.js'
5454
import type { Command } from '../../types/command.js'
5555
import { getCommands } from '../../commands.js'
56-
import { setOriginalCwd } from '../../bootstrap/state.js'
56+
import { setOriginalCwd, switchSession } from '../../bootstrap/state.js'
57+
import type { SessionId } from '../../types/ids.js'
5758
import { enableConfigs } from '../../utils/config.js'
5859
import { FileStateCache } from '../../utils/fileStateCache.js'
5960
import { getDefaultAppState } from '../../state/AppStateStore.js'
@@ -471,6 +472,10 @@ export class AcpAgent implements Agent {
471472
const sessionId = opts.sessionId ?? randomUUID()
472473
const cwd = params.cwd
473474

475+
// Align the global session state so that transcript persistence,
476+
// analytics, and cost tracking use the ACP session ID.
477+
switchSession(sessionId as SessionId)
478+
474479
// Set CWD for the session
475480
setOriginalCwd(cwd)
476481
const previousProcessCwd = process.cwd()
@@ -675,6 +680,8 @@ export class AcpAgent implements Agent {
675680
| undefined,
676681
})
677682
if (fingerprint === existingSession.sessionFingerprint) {
683+
// Align global state so subsequent operations use the correct session
684+
switchSession(params.sessionId as SessionId)
678685
return {
679686
sessionId: params.sessionId,
680687
modes: existingSession.modes,
@@ -687,6 +694,10 @@ export class AcpAgent implements Agent {
687694
await this.teardownSession(params.sessionId)
688695
}
689696

697+
// Align global state BEFORE sessionIdExists() check — the lookup uses
698+
// getSessionId() internally when resolving project-scoped paths.
699+
switchSession(params.sessionId as SessionId)
700+
690701
// Set CWD early so session file lookup can find the right project directory
691702
setOriginalCwd(params.cwd)
692703

0 commit comments

Comments
 (0)