diff --git a/docs/reference/antigravity-native-chat.md b/docs/reference/antigravity-native-chat.md index fa32c8a5c53..c08881143c1 100644 --- a/docs/reference/antigravity-native-chat.md +++ b/docs/reference/antigravity-native-chat.md @@ -10,7 +10,10 @@ The execution host reads `.gemini/antigravity-cli/brain//.system_generated/logs/transcript.jsonl`. A hook-reported transcript path takes precedence. Existing WSL exact-path and host-isolation rules apply; a missing guest transcript must not fall back to a -native host's same-named conversation. +native host's same-named conversation. Direct SSH panes stay in the terminal: +that connection has no native-chat transcript transport, and its absolute file +path must never be opened on the client. Paired runtimes remain eligible because +their own host reads the transcript. Direct SSH chat support remains unfinished. The sanitized fixture in `src/main/native-chat/__fixtures__/antigravity/` records these observed shapes: diff --git a/src/renderer/src/components/native-chat/native-chat-availability.test.ts b/src/renderer/src/components/native-chat/native-chat-availability.test.ts index 409f8c7329f..d5c256f47c7 100644 --- a/src/renderer/src/components/native-chat/native-chat-availability.test.ts +++ b/src/renderer/src/components/native-chat/native-chat-availability.test.ts @@ -90,20 +90,22 @@ describe('canToggleNativeChat', () => { ).toBe(false) }) - // Why: omp discloses no hook transcript path either, so its session file is - // only reachable when this process can read the agent's disk. - it('rejects Model-A SSH omp but accepts it local and runtime-owned', () => { - const forConnection = (connectionId: string | null): boolean => - canToggleNativeChat({ - experimentalNativeChatEnabled: true, - contentType: 'terminal', - launchAgent: 'omp', - nativeChatTranscriptIsLocalReadable: isNativeChatTranscriptLocalReadable(connectionId) - }) - expect(forConnection('ssh-target-1')).toBe(false) - expect(forConnection(null)).toBe(true) - expect(forConnection('runtime-ssh-env-1')).toBe(true) - }) + // Direct SSH has no transcript transport, even when a hook names the remote file. + it.each(['omp', 'antigravity'] as const)( + 'rejects Model-A SSH %s but accepts it local and runtime-owned', + (agent) => { + const forConnection = (connectionId: string | null): boolean => + canToggleNativeChat({ + experimentalNativeChatEnabled: true, + contentType: 'terminal', + launchAgent: agent, + nativeChatTranscriptIsLocalReadable: isNativeChatTranscriptLocalReadable(connectionId) + }) + expect(forConnection('ssh-target-1')).toBe(false) + expect(forConnection(null)).toBe(true) + expect(forConnection('runtime-ssh-env-1')).toBe(true) + } + ) it('lets an existing Model-A SSH Grok chat toggle back to terminal', () => { expect( diff --git a/src/renderer/src/lib/native-chat-initial-view-mode.test.ts b/src/renderer/src/lib/native-chat-initial-view-mode.test.ts index 4348e4d0b5d..36450f805b2 100644 --- a/src/renderer/src/lib/native-chat-initial-view-mode.test.ts +++ b/src/renderer/src/lib/native-chat-initial-view-mode.test.ts @@ -14,7 +14,8 @@ describe('decideInitialAgentTabViewMode', () => { decideInitialAgentTabViewMode({ experimentalNativeChat: true, openAgentTabsInChatByDefault: true, - agent + agent, + nativeChatTranscriptIsLocalReadable: true }) ).toBe('chat') } @@ -77,17 +78,20 @@ describe('decideInitialAgentTabViewMode', () => { ).toBe('chat') }) - it('keeps Model-A SSH omp in the terminal view but opens it locally', () => { - const forConnection = (connectionId: string | null): Tab['viewMode'] => - decideInitialAgentTabViewMode({ - experimentalNativeChat: true, - openAgentTabsInChatByDefault: true, - agent: 'omp', - nativeChatTranscriptIsLocalReadable: isNativeChatTranscriptLocalReadable(connectionId) - }) - expect(forConnection('ssh-target-1')).toBeUndefined() - expect(forConnection(null)).toBe('chat') - }) + it.each(['omp', 'antigravity'] as const)( + 'keeps Model-A SSH %s in the terminal view but opens it locally', + (agent) => { + const forConnection = (connectionId: string | null): Tab['viewMode'] => + decideInitialAgentTabViewMode({ + experimentalNativeChat: true, + openAgentTabsInChatByDefault: true, + agent, + nativeChatTranscriptIsLocalReadable: isNativeChatTranscriptLocalReadable(connectionId) + }) + expect(forConnection('ssh-target-1')).toBeUndefined() + expect(forConnection(null)).toBe('chat') + } + ) it('keeps Model-A SSH Grok in the terminal view', () => { expect( diff --git a/src/shared/native-chat-agent-support.test.ts b/src/shared/native-chat-agent-support.test.ts index 7608f50c98c..bd0b5efaa86 100644 --- a/src/shared/native-chat-agent-support.test.ts +++ b/src/shared/native-chat-agent-support.test.ts @@ -34,11 +34,12 @@ describe('isNativeChatSupportedAgent', () => { }) describe('nativeChatRequiresLocalTranscript', () => { - it('covers the agents whose hook discloses no transcript path', () => { + it('covers agents requiring a runtime-owned transcript filesystem', () => { // Claude/Codex report `transcript_path`; Grok and omp report only an id, so // native chat has to find their file on a disk this process can read. expect(nativeChatRequiresLocalTranscript('grok')).toBe(true) expect(nativeChatRequiresLocalTranscript('omp')).toBe(true) + expect(nativeChatRequiresLocalTranscript('antigravity')).toBe(true) expect(nativeChatRequiresLocalTranscript('claude')).toBe(false) expect(nativeChatRequiresLocalTranscript('openclaude')).toBe(false) expect(nativeChatRequiresLocalTranscript('codex')).toBe(false) diff --git a/src/shared/native-chat-agent-support.ts b/src/shared/native-chat-agent-support.ts index c5db4e55f47..2ff14024005 100644 --- a/src/shared/native-chat-agent-support.ts +++ b/src/shared/native-chat-agent-support.ts @@ -21,13 +21,13 @@ export function isNativeChatSupportedAgent(agent: string | null | undefined): bo return agent != null && NATIVE_CHAT_SUPPORTED_AGENTS.has(agent) } -/** Agents whose hook discloses no transcript path (`extractAgentProviderSession`), - * so native chat can only reach the session file by scanning a sessions root on - * a disk THIS process can read. Under Model-A SSH that disk is the wrong host, - * so the chat view must stay closed instead of loading forever. */ +/** Agents whose chat reads require a filesystem owned by the selected runtime. + * Direct SSH has no transcript transport; a hook path alone is not a local file. */ export function nativeChatRequiresLocalTranscript(agent: string | null | undefined): boolean { const transcriptAgent = resolveNativeChatTranscriptAgent(agent) - return transcriptAgent === 'grok' || transcriptAgent === 'omp' + return ( + transcriptAgent === 'grok' || transcriptAgent === 'omp' || transcriptAgent === 'antigravity' + ) } /** True when the agent renders a digit-commit question selector that ignores