diff --git a/ui/desktop/src/acp/__tests__/sessions.test.ts b/ui/desktop/src/acp/__tests__/sessions.test.ts index f18c93079..bd4150f2b 100644 --- a/ui/desktop/src/acp/__tests__/sessions.test.ts +++ b/ui/desktop/src/acp/__tests__/sessions.test.ts @@ -3,6 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { getAcpClient } from '../acpConnection'; import { acpGetSessionListItem, + acpListSessions, acpLoadSession, acpNewSession, sessionInfoToSession, @@ -44,6 +45,29 @@ describe('ACP sessions', () => { expect(session.name).toBe(''); }); + it('only requests acp session types when explicitly included', async () => { + const client = { + connection: { + agent: { + request: vi.fn().mockResolvedValue({ sessions: [] }), + }, + }, + }; + vi.mocked(getAcpClient).mockResolvedValue( + client as unknown as Awaited> + ); + + await acpListSessions(); + expect(client.connection.agent.request).toHaveBeenLastCalledWith(methods.agent.session.list, { + _meta: { types: ['user', 'scheduled'] }, + }); + + await acpListSessions(undefined, { includeAcp: true }); + expect(client.connection.agent.request).toHaveBeenLastCalledWith(methods.agent.session.list, { + _meta: { types: ['user', 'scheduled', 'acp'] }, + }); + }); + it('returns session info refreshed after loading the ACP session', async () => { const loadedSessionInfo = sessionInfo({ _meta: { diff --git a/ui/desktop/src/acp/sessions.ts b/ui/desktop/src/acp/sessions.ts index d8e322a86..877f88fbc 100644 --- a/ui/desktop/src/acp/sessions.ts +++ b/ui/desktop/src/acp/sessions.ts @@ -135,21 +135,25 @@ function sessionInfoToListItem(s: SessionInfo): SessionListItem { export interface SessionListFilter { keyword?: string; + includeAcp: boolean; } const SESSION_LIST_TYPES = ['user', 'scheduled'] as const; +const SESSION_LIST_TYPES_WITH_ACP = [...SESSION_LIST_TYPES, 'acp'] as const; export async function acpListSessions( cursor?: string | null, - filter?: SessionListFilter + filter: SessionListFilter = { includeAcp: false } ): Promise { const client = await getAcpClient(); const request: ListSessionsRequest = {}; if (cursor) { request.cursor = cursor; } - const meta: Record = { types: SESSION_LIST_TYPES }; - const keyword = filter?.keyword?.trim(); + const meta: Record = { + types: filter.includeAcp ? SESSION_LIST_TYPES_WITH_ACP : SESSION_LIST_TYPES, + }; + const keyword = filter.keyword?.trim(); if (keyword) { meta.query = keyword; } diff --git a/ui/desktop/src/components/sessions/SessionListView.tsx b/ui/desktop/src/components/sessions/SessionListView.tsx index 6a98308ef..54949ec58 100644 --- a/ui/desktop/src/components/sessions/SessionListView.tsx +++ b/ui/desktop/src/components/sessions/SessionListView.tsx @@ -20,6 +20,7 @@ import { } from 'lucide-react'; import { Card } from '../ui/card'; import { Button } from '../ui/button'; +import { Switch } from '../ui/switch'; import { ScrollArea } from '../ui/scroll-area'; import { formatMessageTimestamp } from '../../utils/timeUtils'; import { SearchView } from '../conversation/SearchView'; @@ -174,8 +175,15 @@ const i18n = defineMessages({ id: 'sessions.scheduledJobsCount', defaultMessage: '{count} {count, plural, one {job} other {jobs}}', }, + includeAcpSessions: { + id: 'sessions.includeAcp', + defaultMessage: 'Include ACP sessions', + }, + acpBadge: { id: 'sessions.acpBadge', defaultMessage: 'ACP' }, }); +const INCLUDE_ACP_SESSIONS_KEY = 'sessions_include_acp'; + interface EditSessionModalProps { session: SessionListItem | null; isOpen: boolean; @@ -343,6 +351,12 @@ const SessionListView: React.FC = React.memo(({ onSelectSe const [sharingSessionId, setSharingSessionId] = useState(null); const [nostrEnabled, setNostrEnabled] = useState(true); + const [includeAcpSessions, setIncludeAcpSessions] = useState( + () => localStorage.getItem(INCLUDE_ACP_SESSIONS_KEY) === 'true' + ); + const includeAcpSessionsRef = useRef(includeAcpSessions); + includeAcpSessionsRef.current = includeAcpSessions; + // Search state for debouncing const [searchTerm, setSearchTerm] = useState(''); const debouncedSearchTerm = useDebounce(searchTerm, 300); // 300ms debounce @@ -384,13 +398,13 @@ const SessionListView: React.FC = React.memo(({ onSelectSe }, [debouncedSearchTerm, memoizedAllDateGroups.length]); const loadRemainingSessionPages = useCallback( - async (initialCursor: string, loadId: number, keyword?: string) => { + async (initialCursor: string, loadId: number, keyword: string, includeAcp: boolean) => { let cursor: string | null = initialCursor; setIsPrefetchingSessions(true); try { while (cursor && loadGenerationRef.current === loadId) { - const resp = await acpListSessions(cursor, { keyword }); + const resp = await acpListSessions(cursor, { keyword, includeAcp }); if (loadGenerationRef.current !== loadId) return; cursor = resp.nextCursor; @@ -413,7 +427,10 @@ const SessionListView: React.FC = React.memo(({ onSelectSe ); const loadSessions = useCallback( - async (keyword: string = debouncedSearchTermRef.current) => { + async ( + keyword: string = debouncedSearchTermRef.current, + includeAcp: boolean = includeAcpSessionsRef.current + ) => { const loadId = loadGenerationRef.current + 1; loadGenerationRef.current = loadId; // Only show the skeleton on the first load; subsequent loads (e.g. typing a @@ -427,7 +444,7 @@ const SessionListView: React.FC = React.memo(({ onSelectSe setShowContent(false); } try { - const resp = await acpListSessions(undefined, { keyword }); + const resp = await acpListSessions(undefined, { keyword, includeAcp }); if (loadGenerationRef.current !== loadId) return; hasLoadedRef.current = true; @@ -436,7 +453,7 @@ const SessionListView: React.FC = React.memo(({ onSelectSe }); if (resp.nextCursor) { - void loadRemainingSessionPages(resp.nextCursor, loadId, keyword); + void loadRemainingSessionPages(resp.nextCursor, loadId, keyword, includeAcp); } } catch (err) { if (loadGenerationRef.current !== loadId) return; @@ -468,12 +485,12 @@ const SessionListView: React.FC = React.memo(({ onSelectSe ); useEffect(() => { - loadSessions(debouncedSearchTerm); + loadSessions(debouncedSearchTerm, includeAcpSessions); return () => { // Bump the generation so any in-flight load for the previous keyword is discarded. loadGenerationRef.current += 1; }; - }, [loadSessions, debouncedSearchTerm]); + }, [loadSessions, debouncedSearchTerm, includeAcpSessions]); // Hide Nostr sharing when explicitly disabled via env var (restricted/enterprise bundles) useEffect(() => { @@ -536,6 +553,11 @@ const SessionListView: React.FC = React.memo(({ onSelectSe setSearchTerm(term); }, []); + const handleIncludeAcpSessionsChange = useCallback((checked: boolean) => { + localStorage.setItem(INCLUDE_ACP_SESSIONS_KEY, String(checked)); + setIncludeAcpSessions(checked); + }, []); + // Handle modal close const handleModalClose = useCallback(() => { setShowEditModal(false); @@ -827,6 +849,11 @@ const SessionListView: React.FC = React.memo(({ onSelectSe >

{displayName}

+ {session.sessionType === 'acp' && ( + + {intl.formatMessage(i18n.acpBadge)} + + )}
@@ -1115,6 +1142,14 @@ const SessionListView: React.FC = React.memo(({ onSelectSe

{intl.formatMessage(i18n.chatHistoryDesc, { shortcut: getSearchShortcutText() })}

+
diff --git a/ui/desktop/src/i18n/messages/de.json b/ui/desktop/src/i18n/messages/de.json index 98ad98270..a2fb58691 100644 --- a/ui/desktop/src/i18n/messages/de.json +++ b/ui/desktop/src/i18n/messages/de.json @@ -3783,6 +3783,9 @@ "sessionIndicators.streaming": { "defaultMessage": "Streaming" }, + "sessions.acpBadge": { + "defaultMessage": "ACP" + }, "sessions.action.delete": { "defaultMessage": "Sitzung löschen" }, @@ -3861,6 +3864,9 @@ "sessions.importing": { "defaultMessage": "Wird importiert..." }, + "sessions.includeAcp": { + "defaultMessage": "ACP-Sitzungen einschließen" + }, "sessions.loadingMore": { "defaultMessage": "Weitere Sitzungen werden geladen..." }, diff --git a/ui/desktop/src/i18n/messages/en.json b/ui/desktop/src/i18n/messages/en.json index c65086c94..e781d4a2d 100644 --- a/ui/desktop/src/i18n/messages/en.json +++ b/ui/desktop/src/i18n/messages/en.json @@ -3830,6 +3830,9 @@ "sessionIndicators.streaming": { "defaultMessage": "Streaming" }, + "sessions.acpBadge": { + "defaultMessage": "ACP" + }, "sessions.action.delete": { "defaultMessage": "Delete session" }, @@ -3908,6 +3911,9 @@ "sessions.importing": { "defaultMessage": "Importing..." }, + "sessions.includeAcp": { + "defaultMessage": "Include ACP sessions" + }, "sessions.loadingMore": { "defaultMessage": "Loading more sessions..." }, diff --git a/ui/desktop/src/i18n/messages/es.json b/ui/desktop/src/i18n/messages/es.json index fbef6a1c8..d232c9eba 100644 --- a/ui/desktop/src/i18n/messages/es.json +++ b/ui/desktop/src/i18n/messages/es.json @@ -3784,6 +3784,9 @@ "sessionIndicators.streaming": { "defaultMessage": "Streaming" }, + "sessions.acpBadge": { + "defaultMessage": "ACP" + }, "sessions.action.delete": { "defaultMessage": "Eliminar sesión" }, @@ -3862,6 +3865,9 @@ "sessions.importing": { "defaultMessage": "Importando..." }, + "sessions.includeAcp": { + "defaultMessage": "Incluir sesiones ACP" + }, "sessions.loadingMore": { "defaultMessage": "Cargando más sesiones..." }, diff --git a/ui/desktop/src/i18n/messages/fr.json b/ui/desktop/src/i18n/messages/fr.json index 7a287a8d1..68075696d 100644 --- a/ui/desktop/src/i18n/messages/fr.json +++ b/ui/desktop/src/i18n/messages/fr.json @@ -3784,6 +3784,9 @@ "sessionIndicators.streaming": { "defaultMessage": "Diffusion en cours" }, + "sessions.acpBadge": { + "defaultMessage": "ACP" + }, "sessions.action.delete": { "defaultMessage": "Supprimer la session" }, @@ -3862,6 +3865,9 @@ "sessions.importing": { "defaultMessage": "Importation..." }, + "sessions.includeAcp": { + "defaultMessage": "Inclure les sessions ACP" + }, "sessions.loadingMore": { "defaultMessage": "Chargement de sessions supplémentaires..." }, diff --git a/ui/desktop/src/i18n/messages/hi.json b/ui/desktop/src/i18n/messages/hi.json index f17d47bbc..5703d29b7 100644 --- a/ui/desktop/src/i18n/messages/hi.json +++ b/ui/desktop/src/i18n/messages/hi.json @@ -3784,6 +3784,9 @@ "sessionIndicators.streaming": { "defaultMessage": "स्ट्रीमिंग" }, + "sessions.acpBadge": { + "defaultMessage": "ACP" + }, "sessions.action.delete": { "defaultMessage": "सत्र हटाएँ" }, @@ -3862,6 +3865,9 @@ "sessions.importing": { "defaultMessage": "आयात किया जा रहा है..." }, + "sessions.includeAcp": { + "defaultMessage": "ACP सत्र शामिल करें" + }, "sessions.loadingMore": { "defaultMessage": "अधिक सत्र लोड हो रहे हैं..." }, diff --git a/ui/desktop/src/i18n/messages/id.json b/ui/desktop/src/i18n/messages/id.json index d519ba389..4da1ae58f 100644 --- a/ui/desktop/src/i18n/messages/id.json +++ b/ui/desktop/src/i18n/messages/id.json @@ -3784,6 +3784,9 @@ "sessionIndicators.streaming": { "defaultMessage": "Streaming" }, + "sessions.acpBadge": { + "defaultMessage": "ACP" + }, "sessions.action.delete": { "defaultMessage": "Hapus sesi" }, @@ -3862,6 +3865,9 @@ "sessions.importing": { "defaultMessage": "Mengimpor..." }, + "sessions.includeAcp": { + "defaultMessage": "Sertakan sesi ACP" + }, "sessions.loadingMore": { "defaultMessage": "Memuat lebih banyak sesi..." }, diff --git a/ui/desktop/src/i18n/messages/it.json b/ui/desktop/src/i18n/messages/it.json index 23234854c..1a21e941a 100644 --- a/ui/desktop/src/i18n/messages/it.json +++ b/ui/desktop/src/i18n/messages/it.json @@ -3784,6 +3784,9 @@ "sessionIndicators.streaming": { "defaultMessage": "Streaming" }, + "sessions.acpBadge": { + "defaultMessage": "ACP" + }, "sessions.action.delete": { "defaultMessage": "Elimina sessione" }, @@ -3862,6 +3865,9 @@ "sessions.importing": { "defaultMessage": "Importazione in corso..." }, + "sessions.includeAcp": { + "defaultMessage": "Includi sessioni ACP" + }, "sessions.loadingMore": { "defaultMessage": "Caricamento di altre sessioni..." }, diff --git a/ui/desktop/src/i18n/messages/ja.json b/ui/desktop/src/i18n/messages/ja.json index 0afd9114b..00139a7b0 100644 --- a/ui/desktop/src/i18n/messages/ja.json +++ b/ui/desktop/src/i18n/messages/ja.json @@ -3784,6 +3784,9 @@ "sessionIndicators.streaming": { "defaultMessage": "ストリーミング中" }, + "sessions.acpBadge": { + "defaultMessage": "ACP" + }, "sessions.action.delete": { "defaultMessage": "セッションを削除" }, @@ -3862,6 +3865,9 @@ "sessions.importing": { "defaultMessage": "インポート中..." }, + "sessions.includeAcp": { + "defaultMessage": "ACP セッションを含める" + }, "sessions.loadingMore": { "defaultMessage": "さらにセッションを読み込み中..." }, diff --git a/ui/desktop/src/i18n/messages/ko.json b/ui/desktop/src/i18n/messages/ko.json index 256a10fca..6364949ca 100644 --- a/ui/desktop/src/i18n/messages/ko.json +++ b/ui/desktop/src/i18n/messages/ko.json @@ -3784,6 +3784,9 @@ "sessionIndicators.streaming": { "defaultMessage": "스트리밍" }, + "sessions.acpBadge": { + "defaultMessage": "ACP" + }, "sessions.action.delete": { "defaultMessage": "세션 삭제" }, @@ -3862,6 +3865,9 @@ "sessions.importing": { "defaultMessage": "가져오는 중..." }, + "sessions.includeAcp": { + "defaultMessage": "ACP 세션 포함" + }, "sessions.loadingMore": { "defaultMessage": "추가 세션 로드 중..." }, diff --git a/ui/desktop/src/i18n/messages/ms.json b/ui/desktop/src/i18n/messages/ms.json index f7ae26acf..60a895672 100644 --- a/ui/desktop/src/i18n/messages/ms.json +++ b/ui/desktop/src/i18n/messages/ms.json @@ -3784,6 +3784,9 @@ "sessionIndicators.streaming": { "defaultMessage": "Penstriman" }, + "sessions.acpBadge": { + "defaultMessage": "ACP" + }, "sessions.action.delete": { "defaultMessage": "Padam sesi" }, @@ -3862,6 +3865,9 @@ "sessions.importing": { "defaultMessage": "Mengimport..." }, + "sessions.includeAcp": { + "defaultMessage": "Sertakan sesi ACP" + }, "sessions.loadingMore": { "defaultMessage": "Memuatkan lebih banyak sesi..." }, diff --git a/ui/desktop/src/i18n/messages/pt.json b/ui/desktop/src/i18n/messages/pt.json index 69d82b8ae..fa5f7df0d 100644 --- a/ui/desktop/src/i18n/messages/pt.json +++ b/ui/desktop/src/i18n/messages/pt.json @@ -3784,6 +3784,9 @@ "sessionIndicators.streaming": { "defaultMessage": "Em transmissão" }, + "sessions.acpBadge": { + "defaultMessage": "ACP" + }, "sessions.action.delete": { "defaultMessage": "Eliminar sessão" }, @@ -3862,6 +3865,9 @@ "sessions.importing": { "defaultMessage": "A importar..." }, + "sessions.includeAcp": { + "defaultMessage": "Incluir sessões ACP" + }, "sessions.loadingMore": { "defaultMessage": "A carregar mais sessões..." }, diff --git a/ui/desktop/src/i18n/messages/ru.json b/ui/desktop/src/i18n/messages/ru.json index 411496d7c..c8c396136 100644 --- a/ui/desktop/src/i18n/messages/ru.json +++ b/ui/desktop/src/i18n/messages/ru.json @@ -3784,6 +3784,9 @@ "sessionIndicators.streaming": { "defaultMessage": "Потоковая передача" }, + "sessions.acpBadge": { + "defaultMessage": "ACP" + }, "sessions.action.delete": { "defaultMessage": "Удалить сеанс" }, @@ -3862,6 +3865,9 @@ "sessions.importing": { "defaultMessage": "Импорт..." }, + "sessions.includeAcp": { + "defaultMessage": "Включить сессии ACP" + }, "sessions.loadingMore": { "defaultMessage": "Загрузка дополнительных сеансов..." }, diff --git a/ui/desktop/src/i18n/messages/tr.json b/ui/desktop/src/i18n/messages/tr.json index 526641b8d..1d63852d7 100644 --- a/ui/desktop/src/i18n/messages/tr.json +++ b/ui/desktop/src/i18n/messages/tr.json @@ -3784,6 +3784,9 @@ "sessionIndicators.streaming": { "defaultMessage": "Akış" }, + "sessions.acpBadge": { + "defaultMessage": "ACP" + }, "sessions.action.delete": { "defaultMessage": "Oturumu sil" }, @@ -3862,6 +3865,9 @@ "sessions.importing": { "defaultMessage": "İçe aktarılıyor..." }, + "sessions.includeAcp": { + "defaultMessage": "ACP oturumlarını dahil et" + }, "sessions.loadingMore": { "defaultMessage": "Daha fazla oturum yükleniyor..." }, diff --git a/ui/desktop/src/i18n/messages/vi.json b/ui/desktop/src/i18n/messages/vi.json index 39ca8c11e..d70df271e 100644 --- a/ui/desktop/src/i18n/messages/vi.json +++ b/ui/desktop/src/i18n/messages/vi.json @@ -3784,6 +3784,9 @@ "sessionIndicators.streaming": { "defaultMessage": "Đang truyền phát" }, + "sessions.acpBadge": { + "defaultMessage": "ACP" + }, "sessions.action.delete": { "defaultMessage": "Xóa phiên" }, @@ -3862,6 +3865,9 @@ "sessions.importing": { "defaultMessage": "Đang nhập..." }, + "sessions.includeAcp": { + "defaultMessage": "Bao gồm phiên ACP" + }, "sessions.loadingMore": { "defaultMessage": "Đang tải thêm phiên..." }, diff --git a/ui/desktop/src/i18n/messages/zh-CN.json b/ui/desktop/src/i18n/messages/zh-CN.json index 2dcb7ae1c..0f1fa4130 100644 --- a/ui/desktop/src/i18n/messages/zh-CN.json +++ b/ui/desktop/src/i18n/messages/zh-CN.json @@ -3784,6 +3784,9 @@ "sessionIndicators.streaming": { "defaultMessage": "流式传输中" }, + "sessions.acpBadge": { + "defaultMessage": "ACP" + }, "sessions.action.delete": { "defaultMessage": "删除会话" }, @@ -3862,6 +3865,9 @@ "sessions.importing": { "defaultMessage": "正在导入..." }, + "sessions.includeAcp": { + "defaultMessage": "包含 ACP 会话" + }, "sessions.loadingMore": { "defaultMessage": "正在加载更多会话…" }, diff --git a/ui/desktop/src/i18n/messages/zh-TW.json b/ui/desktop/src/i18n/messages/zh-TW.json index 803e7307f..eef3e2526 100644 --- a/ui/desktop/src/i18n/messages/zh-TW.json +++ b/ui/desktop/src/i18n/messages/zh-TW.json @@ -3784,6 +3784,9 @@ "sessionIndicators.streaming": { "defaultMessage": "串流中" }, + "sessions.acpBadge": { + "defaultMessage": "ACP" + }, "sessions.action.delete": { "defaultMessage": "刪除工作階段" }, @@ -3862,6 +3865,9 @@ "sessions.importing": { "defaultMessage": "正在匯入…" }, + "sessions.includeAcp": { + "defaultMessage": "包含 ACP 工作階段" + }, "sessions.loadingMore": { "defaultMessage": "正在載入更多工作階段…" },