fix(hololake): avoid no-op agent workspace writes
This commit is contained in:
parent
392d205341
commit
dc1a32a52b
6 changed files with 72 additions and 5 deletions
|
|
@ -54,6 +54,8 @@ pnpm package:internal:windows
|
|||
|
||||
内测 Mac 包是 ad-hoc 签名、未公证;Windows 包在没有商业证书时未做 Authenticode 签名。交付时必须明确说明,不能描述成正式签名发行版。
|
||||
|
||||
`tauri.local-candidate.conf.json` 的独立 bundle identifier 只保证候选应用可以与已安装版本并存,不隔离运行数据。当前架构仍从共享的 `com.tolaria.app` 命名空间读取设置、挂载知识库和 Agent 会话。需要无扰动验收时,必须用临时 `XDG_CONFIG_HOME` 启动候选,并在临时配置副本上验证;不能把“没有点击保存”当成“没有写盘”。
|
||||
|
||||
## Windows 构建恢复顺序
|
||||
|
||||
1. `pnpm install --frozen-lockfile`
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import test from 'node:test'
|
|||
|
||||
const readJson = async path => JSON.parse(await readFile(path, 'utf8'))
|
||||
|
||||
test('local native candidate has an isolated macOS identity', async () => {
|
||||
test('local native candidate has an isolated macOS package identity', async () => {
|
||||
const [base, candidate, packageJson] = await Promise.all([
|
||||
readJson('src-tauri/tauri.conf.json'),
|
||||
readJson('src-tauri/tauri.local-candidate.conf.json'),
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -178,4 +178,28 @@ describe('aiWorkspaceSessionStore', () => {
|
|||
expect(store.aiWorkspaceSessionSnapshot('native-chat').messages).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
it('does not rewrite native history when hydration only reads it', async () => {
|
||||
isTauriState.value = true
|
||||
invokeMock.mockImplementation(async (command: string) => {
|
||||
if (command === 'get_ai_workspace_sessions') {
|
||||
return {
|
||||
'native-chat': {
|
||||
messages: [{ userMessage: 'Native', actions: [], response: 'History', id: 'native-message' }],
|
||||
status: 'done',
|
||||
},
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
|
||||
const store = await import('./aiWorkspaceSessionStore')
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(store.aiWorkspaceSessionSnapshot('native-chat').messages).toHaveLength(1)
|
||||
})
|
||||
await new Promise(resolve => setTimeout(resolve, 300))
|
||||
|
||||
expect(invokeMock).not.toHaveBeenCalledWith('save_ai_workspace_sessions', expect.anything())
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -160,7 +160,6 @@ async function syncFromNativeStorage(): Promise<void> {
|
|||
const mergedSessions = mergeStoredSessions(sessionStore.getSnapshot(), nativeSessions)
|
||||
sessionStore.replaceSnapshot(mergedSessions)
|
||||
sessionStore.writeStoredSnapshot(mergedSessions)
|
||||
if (Object.keys(mergedSessions).length > 0) scheduleNativeSessionsWrite(mergedSessions)
|
||||
}
|
||||
|
||||
function ensureSessionStoreSync(): void {
|
||||
|
|
|
|||
Loading…
Reference in a new issue