Files
BrowserOS/packages/browseros-agent/apps/agent/lib/conversations/conversationStorage.ts
Nikhil Sonti 3c629c5929 feat: tool approvals, governance dashboard, and execution history
- Add tool approval system with per-category approval configuration
- Build unified Governance dashboard (renamed from Admin) with pending
  approvals view and execution audit log
- Move execution history tracking into the app shell
- Extract buildChatRequestBody helper and add newtab system prompt
- Add approval config change detection for mid-conversation rebuilds
2026-04-13 09:43:30 -07:00

97 lines
2.8 KiB
TypeScript

import { storage } from '@wxt-dev/storage'
import type { UIMessage } from 'ai'
import { useEffect, useState } from 'react'
import { useSessionInfo } from '../auth/sessionStorage'
import { removeConversationExecutionHistory } from '../execution-history/storage'
import { uploadConversationsToGraphql } from './uploadConversationsToGraphql'
const MAX_CONVERSATIONS = 50
export interface Conversation {
id: string
messages: UIMessage[]
lastMessagedAt: number
}
export const conversationStorage = storage.defineItem<Conversation[]>(
'local:conversations',
{
fallback: [],
},
)
export function useConversations() {
const [conversations, setConversations] = useState<Conversation[]>([])
const { sessionInfo } = useSessionInfo()
useEffect(() => {
// user is logged in, could sync conversations from server here
if (sessionInfo.user?.id && conversations.length > 0) {
uploadConversationsToGraphql(conversations)
}
}, [sessionInfo.user?.id, conversations])
useEffect(() => {
conversationStorage.getValue().then(setConversations)
const unwatch = conversationStorage.watch((newValue) => {
setConversations(newValue ?? [])
})
return unwatch
}, [])
const removeConversation = async (id: string) => {
const current = (await conversationStorage.getValue()) ?? []
await conversationStorage.setValue(current.filter((c) => c.id !== id))
await removeConversationExecutionHistory(id)
}
const saveConversation = async (id: string, messages: UIMessage[]) => {
const current = (await conversationStorage.getValue()) ?? []
const existingIndex = current.findIndex((c) => c.id === id)
if (existingIndex >= 0) {
const existing = current[existingIndex]
const hasContentChanged =
existing.messages.length !== messages.length ||
JSON.stringify(existing.messages) !== JSON.stringify(messages)
if (!hasContentChanged) return
current[existingIndex] = {
...existing,
messages,
lastMessagedAt: Date.now(),
}
await conversationStorage.setValue(current)
} else {
const newConversation: Conversation = {
id,
messages,
lastMessagedAt: Date.now(),
}
const nextConversations = [newConversation, ...current]
const removedConversations = nextConversations.slice(MAX_CONVERSATIONS)
await conversationStorage.setValue(
nextConversations.slice(0, MAX_CONVERSATIONS),
)
await Promise.all(
removedConversations.map((conversation) =>
removeConversationExecutionHistory(conversation.id),
),
)
}
}
const getConversation = (id: string) => {
return conversations.find((c) => c.id === id)
}
return {
conversations,
removeConversation,
saveConversation,
getConversation,
}
}