diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteResponseMapper.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteResponseMapper.ets index c1c6577641..be1ed2271c 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteResponseMapper.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteResponseMapper.ets @@ -58,6 +58,11 @@ export class RemoteResponseMapper { } static sessions(items: SessionItemResponse[]): RemoteSession[] { + return RemoteResponseMapper.visibleSessions(RemoteResponseMapper.allSessions(items)); + } + + /** Maps a server page before mobile-only visibility rules are applied. */ + static allSessions(items: SessionItemResponse[]): RemoteSession[] { return items.map((item: SessionItemResponse) => { const id = item.id || item.session_id || ''; const session: RemoteSession = { @@ -99,6 +104,15 @@ export class RemoteResponseMapper { }); } + /** ACP sessions are created on Desktop and are not controllable on HarmonyOS. */ + static visibleSessions(sessions: RemoteSession[]): RemoteSession[] { + return sessions.filter((session: RemoteSession) => RemoteResponseMapper.isVisibleSession(session)); + } + + static isVisibleSession(session: RemoteSession): boolean { + return !(session.agentType || '').trim().toLowerCase().startsWith('acp:'); + } + static chatMessage(item: ChatMessageResponse): ChatMessage { const text = RemoteResponseMapper.messageText(item); const tools = RemoteResponseMapper.toolsFromExplicitOrItems(item.tools || [], item.items || []); diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionListCache.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionListCache.ets index 8caf8ef1c2..5de0ff8f0d 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionListCache.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionListCache.ets @@ -1,5 +1,6 @@ import { RecentWorkspaceEntry, RemoteSession } from '../model/RemoteModels'; import { RemoteLogger } from './RemoteLogger'; +import { RemoteResponseMapper } from './RemoteResponseMapper'; /** A device's session list as it was last shown. */ export interface RemoteSessionListSlice { @@ -76,7 +77,7 @@ export class RemoteSessionListCache { return RemoteSessionListCache.empty(); } try { - return await this.store.loadLastList(); + return RemoteSessionListCache.visible(await this.store.loadLastList()); } catch (err) { RemoteLogger.error(`remote session list cache read failed: ${RemoteSessionListCache.reason(err)}`); return RemoteSessionListCache.empty(); @@ -90,7 +91,7 @@ export class RemoteSessionListCache { return RemoteSessionListCache.empty(); } try { - return await this.store.loadList(deviceKey); + return RemoteSessionListCache.visible(await this.store.loadList(deviceKey)); } catch (err) { RemoteLogger.error(`remote session list cache read failed: ${RemoteSessionListCache.reason(err)}`); return RemoteSessionListCache.empty(); @@ -106,11 +107,12 @@ export class RemoteSessionListCache { */ async save(sessions: RemoteSession[], hasMore: boolean): Promise { const deviceKey = this.scope(); - if (deviceKey.length === 0 || sessions.length === 0) { + const visibleSessions = RemoteResponseMapper.visibleSessions(sessions); + if (deviceKey.length === 0 || visibleSessions.length === 0) { return; } - const kept = sessions.slice(0, CACHED_SESSIONS_PER_DEVICE); - const keptHasMore = hasMore || kept.length < sessions.length; + const kept = visibleSessions.slice(0, CACHED_SESSIONS_PER_DEVICE); + const keptHasMore = hasMore || kept.length < visibleSessions.length; const signature = RemoteSessionListCache.signature(kept, keptHasMore); if (this.writtenScope === deviceKey && this.writtenSignature === signature) { return; @@ -167,6 +169,13 @@ export class RemoteSessionListCache { return { sessions: [], hasMore: false }; } + private static visible(slice: RemoteSessionListSlice): RemoteSessionListSlice { + return { + sessions: RemoteResponseMapper.visibleSessions(slice.sessions), + hasMore: slice.hasMore + }; + } + private static reason(err: Object): string { return err instanceof Error ? err.message : `${err}`; } diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionManager.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionManager.ets index ed4a3ef47d..7fce39eca0 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionManager.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/RemoteSessionManager.ets @@ -205,16 +205,7 @@ export class RemoteSessionManager implements RemoteChatCommandClient, RemoteFile const workspacePath = this.workspace?.path || ''; const normalizedAgentType = RemoteSessionManager.normalizedSessionFilter(agentType); const trimmedQuery = query.trim(); - if (!normalizedAgentType) { - const command = RemoteCommandFactory.listSessions(workspacePath, limit, offset, trimmedQuery); - const response = await this.send(command); - return { - sessions: RemoteResponseMapper.sessions(response.sessions || []), - hasMore: response.has_more || false - }; - } - - const pageSize = Math.max(100, limit); + const pageSize = normalizedAgentType ? Math.max(100, limit) : Math.max(1, limit); const targetCount = offset + limit; const filteredSessions: RemoteSession[] = []; let pageOffset = 0; @@ -222,14 +213,15 @@ export class RemoteSessionManager implements RemoteChatCommandClient, RemoteFile while (hasMore && filteredSessions.length < targetCount) { const command = RemoteCommandFactory.listSessions(workspacePath, pageSize, pageOffset, trimmedQuery); const response = await this.send(command); - const sessions = RemoteResponseMapper.sessions(response.sessions || []); + const sessions = RemoteResponseMapper.allSessions(response.sessions || []); for (const session of sessions) { - if (RemoteSessionManager.agentMatchesFilter(session.agentType, normalizedAgentType)) { + if (RemoteResponseMapper.isVisibleSession(session) && + (!normalizedAgentType || RemoteSessionManager.agentMatchesFilter(session.agentType, normalizedAgentType))) { filteredSessions.push(session); } } hasMore = response.has_more || false; - pageOffset += pageSize; + pageOffset += sessions.length; if (sessions.length === 0) { break; } diff --git a/src/apps/mobile/harmonyos/entry/src/test/RemoteControllersUnit.test.ets b/src/apps/mobile/harmonyos/entry/src/test/RemoteControllersUnit.test.ets index ff7bc527bb..6daaf1dccd 100644 --- a/src/apps/mobile/harmonyos/entry/src/test/RemoteControllersUnit.test.ets +++ b/src/apps/mobile/harmonyos/entry/src/test/RemoteControllersUnit.test.ets @@ -1436,6 +1436,24 @@ export default function remoteControllersUnitTest() { expect(fromSecond.sessions[0].id).assertEqual('session-2'); }); + it('does not restore or persist ACP sessions', 0, async () => { + const store = new FakeRemoteSessionListStore(); + const cache = await readyRemoteSessionListCache(store, 'desktop-a'); + const acp = remoteSession('acp-session', 'ACP'); + acp.agentType = 'acp:codex'; + const native = remoteSession('native-session', 'Native'); + await store.saveList('desktop-a', [acp, native], false); + + const restored = await cache.restoreLast(); + expect(restored.sessions.length).assertEqual(1); + expect(restored.sessions[0].id).assertEqual('native-session'); + + await cache.save([acp, native], false); + const saved = await store.loadList('desktop-a'); + expect(saved.sessions.length).assertEqual(1); + expect(saved.sessions[0].id).assertEqual('native-session'); + }); + it('writes once for a list that has not changed', 0, async () => { const store = new FakeRemoteSessionListStore(); const cache = await readyRemoteSessionListCache(store); diff --git a/src/apps/mobile/harmonyos/entry/src/test/TransportAndGeneralChatUnit.test.ets b/src/apps/mobile/harmonyos/entry/src/test/TransportAndGeneralChatUnit.test.ets index bda292add4..788eb094dc 100644 --- a/src/apps/mobile/harmonyos/entry/src/test/TransportAndGeneralChatUnit.test.ets +++ b/src/apps/mobile/harmonyos/entry/src/test/TransportAndGeneralChatUnit.test.ets @@ -131,6 +131,7 @@ import { ReadFileResult, RemoteSession, SelectedImageAttachment, + SessionItemResponse, SessionListResult, SessionMessagesResult, SessionSummary, @@ -1554,6 +1555,29 @@ export default function transportAndGeneralChatUnitTest() { expect(sessions[0].workspaceName).assertEqual('Repo'); }); + it('hides ACP sessions while preserving raw pages for pagination', 0, () => { + const items: SessionItemResponse[] = [{ + session_id: 'native-session', + name: 'Native', + agent_type: 'agentic' + }, { + session_id: 'acp-session', + name: 'ACP', + agent_type: 'acp:codex' + }, { + session_id: 'future-acp-session', + name: 'Future ACP', + agent_type: ' ACP:gemini ' + }]; + + const visible = RemoteResponseMapper.sessions(items); + const raw = RemoteResponseMapper.allSessions(items); + + expect(visible.length).assertEqual(1); + expect(visible[0].id).assertEqual('native-session'); + expect(raw.length).assertEqual(3); + }); + it('maps alternate session time fields', 0, () => { const sessions = RemoteResponseMapper.sessions([{ session_id: 'session-time',