fix(chat): resync runtime mode before user turns

Ensure Solo and coordinator modes are replayed before sending a user message so the server cannot lose the runtime mode after cleanup or reconnects.

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>
This commit is contained in:
你的姓名 2026-06-14 00:20:15 +08:00
parent 0136fbb9ff
commit b3e74c4f56
2 changed files with 45 additions and 3 deletions

View File

@ -186,7 +186,7 @@ describe('chatStore history mapping', () => {
sessionStoreSnapshot.sessions = []
cliTaskStoreSnapshot.tasks = []
cliTaskStoreSnapshot.sessionId = null
useSessionRuntimeStore.setState({ selections: {} })
useSessionRuntimeStore.setState({ selections: {}, coordinatorModes: {}, soloPipelineModes: {} })
localStorage.clear()
useChatStore.setState({
...initialState,
@ -1606,6 +1606,31 @@ describe('chatStore history mapping', () => {
)
})
it('replays Solo Pipeline mode immediately before sending a user turn', () => {
useSessionRuntimeStore.setState({ coordinatorModes: {}, soloPipelineModes: { [TEST_SESSION_ID]: true } })
useChatStore.setState({
sessions: {
[TEST_SESSION_ID]: makeSession({ chatState: 'idle' }),
},
})
useChatStore.getState().sendMessage(TEST_SESSION_ID, '继续 solo')
expect(sendMock).toHaveBeenNthCalledWith(1, TEST_SESSION_ID, {
type: 'set_coordinator_mode',
enabled: false,
})
expect(sendMock).toHaveBeenNthCalledWith(2, TEST_SESSION_ID, {
type: 'set_pipeline_mode',
flavor: 'solo',
})
expect(sendMock).toHaveBeenNthCalledWith(3, TEST_SESSION_ID, {
type: 'user_message',
content: '继续 solo',
attachments: undefined,
})
})
it('can send a visual selection turn without rendering the full model prompt as user text', () => {
useChatStore.setState({
sessions: {
@ -4460,7 +4485,7 @@ describe('chatStore context-exhausted suggestion (方案3)', () => {
sendMock.mockReset()
getMemberBySessionIdMock.mockReset()
getMemberBySessionIdMock.mockReturnValue(null)
useSessionRuntimeStore.setState({ selections: {} })
useSessionRuntimeStore.setState({ selections: {}, coordinatorModes: {}, soloPipelineModes: {} })
useChatStore.setState({ ...initialState, sessions: {} })
// Reset the module-level compaction-thrash tracking for this session id.
useChatStore.getState().clearMessages(SID)
@ -4521,7 +4546,7 @@ describe('chatStore message queue', () => {
sendMock.mockReset()
getMemberBySessionIdMock.mockReset()
getMemberBySessionIdMock.mockReturnValue(null)
useSessionRuntimeStore.setState({ selections: {} })
useSessionRuntimeStore.setState({ selections: {}, coordinatorModes: {}, soloPipelineModes: {} })
useChatStore.setState({ ...initialState, sessions: {} })
})

View File

@ -1176,6 +1176,23 @@ export const useChatStore = create<ChatStore>((set, get) => ({
// 方案3: count this as a real user turn so rapid re-compaction (thrash)
// can be told apart from compactions spread across genuine work.
noteUserTurnForCompactionThrash(sessionId)
// 每次真实用户回合前都把服务端运行模式对齐一次。
// 这样即使 renderer 重连或服务端清理了内存标记UI 里仍开启的
// Solo/协调模式也会在本轮消息前重新生效。
const runtimeModes = useSessionRuntimeStore.getState()
const coordinatorEnabled = runtimeModes.coordinatorModes[sessionId] ?? false
const soloPipelineEnabled = runtimeModes.soloPipelineModes[sessionId] ?? false
if (soloPipelineEnabled) {
wsManager.send(sessionId, { type: 'set_coordinator_mode', enabled: false })
wsManager.send(sessionId, { type: 'set_pipeline_mode', flavor: 'solo' })
} else if (coordinatorEnabled) {
wsManager.send(sessionId, { type: 'set_pipeline_mode', flavor: 'normal' })
wsManager.send(sessionId, { type: 'set_coordinator_mode', enabled: true })
} else {
wsManager.send(sessionId, { type: 'set_coordinator_mode', enabled: false })
wsManager.send(sessionId, { type: 'set_pipeline_mode', flavor: 'normal' })
}
wsManager.send(sessionId, { type: 'user_message', content, attachments: serverAttachments })
},