fix: keep direct SSH Antigravity chat on its execution host

This commit is contained in:
Neil
2026-09-19 04:57:41 -07:00
parent e5dcac8fe4
commit 8bcb02cdde
5 changed files with 43 additions and 33 deletions
+4 -1
View File
@@ -10,7 +10,10 @@ The execution host reads
`.gemini/antigravity-cli/brain/<conversation-id>/.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:
@@ -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(
@@ -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(
+2 -1
View File
@@ -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)
+5 -5
View File
@@ -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