diff --git a/apps/backend/src/handlers/agent.ts b/apps/backend/src/handlers/agent.ts index 9959a58a0..e2ad15b80 100644 --- a/apps/backend/src/handlers/agent.ts +++ b/apps/backend/src/handlers/agent.ts @@ -15,6 +15,7 @@ import { buildImageUrl } from '../utils/image'; interface HandleAgentMessageInput extends AgentRequest { userId: string; projectId: string | undefined; + isProjectAdmin: boolean; } interface HandleAgentMessageResult { @@ -25,7 +26,7 @@ interface HandleAgentMessageResult { } export const handleAgentRoute = async (opts: HandleAgentMessageInput): Promise => { - const { userId, message, messageToEditId, model, mentions, projectId, adminMode } = opts; + const { userId, message, messageToEditId, model, mentions, projectId, isProjectAdmin } = opts; if (!projectId) { throw new HandlerError('BAD_REQUEST', noProjectMessage()); @@ -33,6 +34,11 @@ export const handleAgentRoute = async (opts: HandleAgentMessageInput): Promise => { + const [row] = await db + .select({ source: s.chatMessage.source }) + .from(s.chatMessage) + .where( + and(eq(s.chatMessage.chatId, chatId), eq(s.chatMessage.role, 'user'), isNull(s.chatMessage.supersededAt)), + ) + .orderBy(desc(s.chatMessage.createdAt)) + .limit(1); + return row?.source === 'admin'; +}; + /** Marks all messages from a given message id onwards as superseeded (won't be used in the conversation anymore). */ export const supersedeMessagesFrom = async (chatId: string, fromMessageId: string): Promise => { await db.transaction(async (t) => { diff --git a/apps/backend/src/routes/agent.ts b/apps/backend/src/routes/agent.ts index 265983ad9..11c96560f 100644 --- a/apps/backend/src/routes/agent.ts +++ b/apps/backend/src/routes/agent.ts @@ -31,6 +31,7 @@ export const agentRoutes = async (app: App) => { projectId, ...body, adminMode: body.adminMode && isProjectAdmin, + isProjectAdmin, }); posthog.capture(user.id, PostHogEvent.MessageSent, { diff --git a/apps/frontend/src/hooks/use-agent.ts b/apps/frontend/src/hooks/use-agent.ts index e58d1f116..a545d02ef 100644 --- a/apps/frontend/src/hooks/use-agent.ts +++ b/apps/frontend/src/hooks/use-agent.ts @@ -187,7 +187,7 @@ export const useAgent = ({ disableNavigation = false }: { disableNavigation?: bo model: activeSelectedModelRef.current ?? undefined, mentions: mentions.length > 0 ? mentions : undefined, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, - adminMode: adminModeAtSend || undefined, + adminMode: adminModeAtSend, }, }; }, @@ -257,9 +257,9 @@ export const useAgent = ({ disableNavigation = false }: { disableNavigation?: bo } }, [error]); // eslint-disable-line react-hooks/exhaustive-deps - // Carry admin mode across an admin conversation: enable it for chats whose first or - // last user message was sent from admin mode, and disable it for normal chats. A - // freshly created chat keeps its current mode so follow-ups stay in admin mode. + // Carry admin mode across an admin conversation: on load, start in the mode of the latest + // user turn (derived from server data, which carries `source`). Once loaded, a manual toggle + // always wins — this effect syncs a chat only once, so it never overrides a later user toggle. const syncedAdminChatRef = useRef(undefined); useEffect(() => { if (!chatId) { @@ -271,14 +271,14 @@ export const useAgent = ({ disableNavigation = false }: { disableNavigation?: bo syncedAdminChatRef.current = chatId; return; } - if (chat.isLoading || messages.length === 0 || syncedAdminChatRef.current === chatId) { + const serverMessages = chat.data?.messages; + if (chat.isLoading || !serverMessages || serverMessages.length === 0 || syncedAdminChatRef.current === chatId) { return; } syncedAdminChatRef.current = chatId; - const userMessages = messages.filter((m) => m.role === 'user'); - const conversationIsAdmin = userMessages.at(0)?.source === 'admin' || userMessages.at(-1)?.source === 'admin'; - setAdminMode(conversationIsAdmin); - }, [chatId, chat.isLoading, messages, setAdminMode]); + const lastUserMessage = serverMessages.filter((m) => m.role === 'user').at(-1); + setAdminMode(lastUserMessage?.source === 'admin'); + }, [chatId, chat.isLoading, chat.data?.messages, setAdminMode]); const stopAgent = useCallback(async () => { if (!chatId) {