fix(hololake): avoid no-op agent workspace writes

This commit is contained in:
冰朔 2026-08-12 07:18:46 +08:00
commit dc1a32a52b
6 changed files with 72 additions and 5 deletions

View file

@ -360,6 +360,30 @@ describe('AiWorkspace', () => {
expect(screen.queryByRole('button', { name: 'Current work' })).toBeNull()
})
it('does not rewrite unchanged conversation settings when the workspace opens', async () => {
const onConversationSettingsChange = vi.fn()
render(
<AiWorkspace
open
mode="side"
aiAgentsStatus={installedStatuses()}
aiModelProviders={providers}
conversationSettings={[
{ id: 'live-chat', title: 'Current work', target_id: null, archived: false },
]}
vaultPath="/tmp/vault"
onClose={vi.fn()}
onConversationSettingsChange={onConversationSettingsChange}
/>,
)
await waitFor(() => {
expect(screen.getByRole('button', { name: 'Current work' })).toBeTruthy()
})
expect(onConversationSettingsChange).not.toHaveBeenCalled()
})
it('recovers stored sessions that no longer have conversation metadata', async () => {
setAiWorkspaceSessionMessages('orphaned-chat', [{
userMessage: 'Recovered prompt',

View file

@ -197,6 +197,20 @@ function conversationsToSettings(conversations: AiConversation[]): AiWorkspaceCo
}))
}
function conversationSettingsEqual(
current: AiWorkspaceConversationSetting[],
stored: AiWorkspaceConversationSetting[] | null | undefined,
): boolean {
if (!stored || current.length !== stored.length) return false
return current.every((conversation, index) => {
const persisted = stored[index]
return conversation.archived === (persisted.archived === true)
&& conversation.id === persisted.id
&& conversation.target_id === (persisted.target_id?.trim() || null)
&& conversation.title === persisted.title
})
}
export function activeConversationForState(
conversations: AiConversation[],
activeId: ConversationId,
@ -370,10 +384,12 @@ function initialActiveId(conversations: AiConversation[], requestedId: Conversat
function useConversationSettingsPersistence({
conversations,
onSettingsChange,
settings,
settingsReady,
}: {
conversations: AiConversation[]
onSettingsChange?: (conversations: AiWorkspaceConversationSetting[]) => void
settings?: AiWorkspaceConversationSetting[] | null
settingsReady: boolean
}) {
const onSettingsChangeRef = useRef(onSettingsChange)
@ -384,8 +400,10 @@ function useConversationSettingsPersistence({
useEffect(() => {
if (!settingsReady) return
onSettingsChangeRef.current?.(conversationsToSettings(conversations))
}, [conversations, settingsReady])
const nextSettings = conversationsToSettings(conversations)
if (conversationSettingsEqual(nextSettings, settings)) return
onSettingsChangeRef.current?.(nextSettings)
}, [conversations, settings, settingsReady])
}
function useTitleConversationFromAnswer(setConversations: SetConversations) {
@ -490,7 +508,7 @@ export function useConversations({
const titleConversationFromAnswer = useTitleConversationFromAnswer(setConversations)
const updateDefaultConversationTargets = useUpdateDefaultConversationTargets(setConversations)
useConversationSettingsPersistence({ conversations, onSettingsChange, settingsReady })
useConversationSettingsPersistence({ conversations, onSettingsChange, settings, settingsReady })
return {
activeId, addConversation, archiveConversation, closeConversation, conversations, deleteConversation, forkConversation,