mirror of
https://github.com/stablyai/orca.git
synced 2026-09-21 16:02:20 +00:00
fix(antigravity): wait for host-confirmed composer before continuation paste
This commit is contained in:
@@ -349,3 +349,25 @@ The honest summary is that this is a screen-shaped problem being solved with lin
|
||||
rule over the derived tail can be made much better than what ships today, but the durable fix is to
|
||||
ask the terminal emulator what the bottom row of the screen actually is, rather than inferring it
|
||||
from a byte stream that was written with cursor addressing.
|
||||
|
||||
## 2026-09-19: renderer continuation delivery
|
||||
|
||||
The renderer's generated-prompt path still used a separate bracketed-paste/quiet
|
||||
observer. A wrapper that waits 12 seconds before executing installed agy 1.2.7
|
||||
reproduced #18088: the shell echoed the continuation before agy started, and the
|
||||
agent opened with an empty composer and no matching saved first turn.
|
||||
|
||||
Antigravity draft delivery now resolves the pane on its owning host and uses
|
||||
`terminal.wait` with `tui-idle`, reusing the recorded-screen classifier above.
|
||||
It allows 60 seconds for readiness and never falls back to process presence.
|
||||
The same live PTY and host-published Antigravity identity are checked before
|
||||
writing. Paired hosts must advertise `terminal.antigravity-visible-readiness.v1`;
|
||||
older hosts leave the context unsent and use the existing failure notice.
|
||||
|
||||
With the delayed wrapper, no context was visible at 10.4 seconds, before agent
|
||||
startup. Once ready, the saved first user turn exactly matched the generated
|
||||
prompt. A 36,868-character continuation also matched byte-for-byte (SHA-256),
|
||||
without transcript truncation. The final identity-checked implementation passed
|
||||
another delayed launch with an exact 920-character first-turn match. Generation
|
||||
still failed with the existing 401 authentication error; this verifies delivery,
|
||||
not a successful continuation task. Real Windows/WSL/SSH runs remain unverified.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { waitForAntigravityDraftReady } from './antigravity-draft-readiness'
|
||||
import type { GlobalSettings } from '../../../shared/global-settings-types'
|
||||
import type { TuiAgent } from '../../../shared/tui-agent'
|
||||
import { TUI_AGENT_CONFIG } from '../../../shared/tui-agent-config'
|
||||
@@ -102,6 +103,7 @@ export async function pasteDraftWhenAgentReady(args: {
|
||||
tabId,
|
||||
spawnTimeoutMs: PTY_SPAWN_TIMEOUT_MS,
|
||||
readinessTimeoutMs,
|
||||
agent,
|
||||
readySignal,
|
||||
settings
|
||||
})
|
||||
@@ -112,6 +114,10 @@ export async function pasteDraftWhenAgentReady(args: {
|
||||
|
||||
const { ptyId } = readiness
|
||||
if (!readiness.ready) {
|
||||
if (agent === 'antigravity') {
|
||||
onTimeout?.()
|
||||
return false
|
||||
}
|
||||
// Why: fast-starting TUIs can emit the paste-ready escape sequence before
|
||||
// this sidecar subscription attaches. If process/title inspection says the
|
||||
// launched agent owns the PTY, fall back to a best-effort paste instead of
|
||||
@@ -154,8 +160,15 @@ export async function pasteDraftToAgentPtyWhenReady(args: {
|
||||
const settings = getSettingsForAgentTabRuntimeOwner(tabId)
|
||||
const readySignal = agentConfig?.draftPasteReadySignal ?? 'render-quiet-after-bracketed-paste'
|
||||
const budget = resolveDraftPasteReadyTimeoutMs(agent, timeoutMs)
|
||||
const ready = await waitForAgentDraftInputReady(ptyId, budget, readySignal, settings)
|
||||
const ready =
|
||||
agent === 'antigravity'
|
||||
? await waitForAntigravityDraftReady(tabId, ptyId, budget, settings)
|
||||
: await waitForAgentDraftInputReady(ptyId, budget, readySignal, settings)
|
||||
if (!ready) {
|
||||
if (agent === 'antigravity') {
|
||||
onTimeout?.()
|
||||
return false
|
||||
}
|
||||
const fallbackReady = agentConfig
|
||||
? await waitForExpectedAgentOnPty(ptyId, agentConfig.expectedProcess, 1000, settings)
|
||||
: false
|
||||
@@ -240,6 +253,7 @@ function waitForAgentDraftInputReadyOnTab(args: {
|
||||
tabId: string
|
||||
spawnTimeoutMs: number
|
||||
readinessTimeoutMs: number
|
||||
agent?: TuiAgent
|
||||
readySignal: Parameters<typeof waitForAgentDraftInputReady>[2]
|
||||
settings: Pick<GlobalSettings, 'activeRuntimeEnvironmentId'> | null | undefined
|
||||
}): Promise<{ ptyId: string; ready: boolean } | null> {
|
||||
@@ -271,12 +285,16 @@ function waitForAgentDraftInputReadyOnTab(args: {
|
||||
unsubscribeStore?.()
|
||||
// Why: Zustand subscribers run inside updateTabPtyId. Registering the
|
||||
// sidecar here precedes the transport's immediate pre-handler drain.
|
||||
void waitForAgentDraftInputReady(
|
||||
ptyId,
|
||||
args.readinessTimeoutMs,
|
||||
args.readySignal,
|
||||
args.settings
|
||||
).then((ready) => finish({ ptyId, ready }))
|
||||
const readiness =
|
||||
args.agent === 'antigravity'
|
||||
? waitForAntigravityDraftReady(args.tabId, ptyId, args.readinessTimeoutMs, args.settings)
|
||||
: waitForAgentDraftInputReady(
|
||||
ptyId,
|
||||
args.readinessTimeoutMs,
|
||||
args.readySignal,
|
||||
args.settings
|
||||
)
|
||||
void readiness.then((ready) => finish({ ptyId, ready }))
|
||||
}
|
||||
const bindFromState = (state: ReturnType<typeof useAppStore.getState>): void => {
|
||||
const ptyId = state.ptyIdsByTabId[args.tabId]?.[0]
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { pasteDraftToAgentPtyWhenReady, pasteDraftWhenAgentReady } from './agent-paste-draft'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
hostReady: vi.fn(),
|
||||
shellReady: vi.fn(),
|
||||
inspect: vi.fn(),
|
||||
send: vi.fn(),
|
||||
processReady: vi.fn()
|
||||
}))
|
||||
vi.mock('./antigravity-draft-readiness', () => ({ waitForAntigravityDraftReady: mocks.hostReady }))
|
||||
vi.mock('./agent-draft-readiness', () => ({ waitForAgentDraftInputReady: mocks.shellReady }))
|
||||
vi.mock('./agent-ready-wait', () => ({ waitForAgentReady: mocks.processReady }))
|
||||
vi.mock('@/store', () => ({
|
||||
useAppStore: {
|
||||
getState: () => ({ settings: {}, ptyIdsByTabId: { tab: ['pty'] }, tabsByWorktree: {} }),
|
||||
subscribe: () => () => {}
|
||||
}
|
||||
}))
|
||||
vi.mock('@/runtime/runtime-terminal-inspection', () => ({
|
||||
sendRuntimePtyInputVerified: mocks.send,
|
||||
inspectRuntimeTerminalProcess: mocks.inspect
|
||||
}))
|
||||
|
||||
describe('Antigravity continuation delivery', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
vi.stubGlobal('window', { setTimeout, clearTimeout })
|
||||
mocks.hostReady.mockReset()
|
||||
mocks.shellReady
|
||||
.mockReset()
|
||||
.mockImplementation(
|
||||
() => new Promise<boolean>((resolve) => setTimeout(() => resolve(true), 1500))
|
||||
)
|
||||
mocks.processReady.mockReset().mockResolvedValue({ ready: true })
|
||||
mocks.inspect
|
||||
.mockReset()
|
||||
.mockResolvedValue({ foregroundProcess: 'agy', hasChildProcesses: true })
|
||||
mocks.send.mockReset().mockResolvedValue(true)
|
||||
})
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it('leaves context unwritten through a quiet shell until the host confirms the composer', async () => {
|
||||
mocks.hostReady.mockImplementation(
|
||||
() => new Promise<boolean>((resolve) => setTimeout(() => resolve(true), 12000))
|
||||
)
|
||||
const waiting = pasteDraftWhenAgentReady({
|
||||
tabId: 'tab',
|
||||
agent: 'antigravity',
|
||||
content: 'continue context',
|
||||
submit: true,
|
||||
forcePaste: true
|
||||
})
|
||||
await vi.advanceTimersByTimeAsync(10000)
|
||||
expect(mocks.send).not.toHaveBeenCalled()
|
||||
expect(mocks.shellReady).not.toHaveBeenCalled()
|
||||
await vi.advanceTimersByTimeAsync(2100)
|
||||
await expect(waiting).resolves.toBe(true)
|
||||
expect(mocks.hostReady).toHaveBeenCalledWith('tab', 'pty', 60000, {})
|
||||
expect(mocks.send).toHaveBeenLastCalledWith({}, 'pty', '\r')
|
||||
})
|
||||
|
||||
it.each(['tab', 'pty'] as const)('rejects process-only fallback on the %s path', async (path) => {
|
||||
mocks.hostReady.mockResolvedValue(false)
|
||||
const onTimeout = vi.fn()
|
||||
const args = {
|
||||
tabId: 'tab',
|
||||
ptyId: 'pty',
|
||||
agent: 'antigravity' as const,
|
||||
content: 'preserve context',
|
||||
submit: true,
|
||||
forcePaste: true,
|
||||
onTimeout
|
||||
}
|
||||
const waiting =
|
||||
path === 'tab' ? pasteDraftWhenAgentReady(args) : pasteDraftToAgentPtyWhenReady(args)
|
||||
await expect(waiting).resolves.toBe(false)
|
||||
expect(onTimeout).toHaveBeenCalledOnce()
|
||||
expect(mocks.inspect).not.toHaveBeenCalled()
|
||||
expect(mocks.processReady).not.toHaveBeenCalled()
|
||||
expect(mocks.send).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,172 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ANTIGRAVITY_VISIBLE_READINESS_RUNTIME_CAPABILITY as capability } from '../../../shared/protocol-version'
|
||||
import type * as RuntimeRpcClient from '@/runtime/runtime-rpc-client'
|
||||
import { waitForAntigravityDraftReady } from './antigravity-draft-readiness'
|
||||
|
||||
const leaf = 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee'
|
||||
const mocks = vi.hoisted(() => {
|
||||
const state: {
|
||||
ptyIdsByTabId: Record<string, string[]>
|
||||
terminalLayoutsByTabId: Record<string, { ptyIdsByLeafId: Record<string, string> }>
|
||||
} = { ptyIdsByTabId: { tab: ['pty'] }, terminalLayoutsByTabId: {} }
|
||||
return {
|
||||
call: vi.fn(),
|
||||
capabilities: vi.fn(),
|
||||
remoteCapability: vi.fn(),
|
||||
state
|
||||
}
|
||||
})
|
||||
vi.mock('@/store', () => ({ useAppStore: { getState: () => mocks.state } }))
|
||||
vi.mock('@/runtime/local-runtime-capabilities', () => ({
|
||||
ensureLocalRuntimeCapabilities: mocks.capabilities
|
||||
}))
|
||||
vi.mock('@/runtime/runtime-rpc-client', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof RuntimeRpcClient>()
|
||||
return {
|
||||
...actual,
|
||||
callRuntimeRpc: mocks.call,
|
||||
runtimeEnvironmentSupportsCapability: mocks.remoteCapability,
|
||||
getActiveRuntimeTarget: (settings?: { activeRuntimeEnvironmentId?: string }) =>
|
||||
settings?.activeRuntimeEnvironmentId
|
||||
? { kind: 'environment', environmentId: settings.activeRuntimeEnvironmentId }
|
||||
: { kind: 'local' }
|
||||
}
|
||||
})
|
||||
|
||||
const ready = {
|
||||
handle: 'term_test',
|
||||
condition: 'tui-idle',
|
||||
satisfied: true,
|
||||
status: 'running',
|
||||
exitCode: null
|
||||
}
|
||||
|
||||
describe('Antigravity host-owned draft readiness', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
vi.stubGlobal('window', { setTimeout, clearTimeout })
|
||||
mocks.state.ptyIdsByTabId = { tab: ['pty'] }
|
||||
mocks.state.terminalLayoutsByTabId = { tab: { ptyIdsByLeafId: { [leaf]: 'pty' } } }
|
||||
mocks.capabilities.mockReset().mockResolvedValue([capability])
|
||||
mocks.remoteCapability.mockReset().mockResolvedValue(true)
|
||||
mocks.call.mockReset().mockImplementation(async (_target, method) =>
|
||||
method === 'terminal.show'
|
||||
? {
|
||||
terminal: {
|
||||
handle: 'term_test',
|
||||
ptyId: 'pty',
|
||||
connected: true,
|
||||
writable: true,
|
||||
agentIdentity: 'antigravity'
|
||||
}
|
||||
}
|
||||
: method === 'terminal.resolvePane'
|
||||
? { terminal: { handle: 'term_test', tabId: 'tab', leafId: leaf, ptyId: 'pty' } }
|
||||
: { wait: ready }
|
||||
)
|
||||
})
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it('waits for the exact pane on its owning host', async () => {
|
||||
await expect(waitForAntigravityDraftReady('tab', 'pty', 60000, null)).resolves.toBe(true)
|
||||
expect(mocks.call).toHaveBeenCalledWith(
|
||||
{ kind: 'local' },
|
||||
'terminal.resolvePane',
|
||||
{ paneKey: `tab:${leaf}` },
|
||||
expect.anything()
|
||||
)
|
||||
expect(mocks.call).toHaveBeenCalledWith(
|
||||
{ kind: 'local' },
|
||||
'terminal.wait',
|
||||
{ terminal: 'term_test', for: 'tui-idle', timeoutMs: 60000 },
|
||||
expect.anything()
|
||||
)
|
||||
})
|
||||
|
||||
it('routes a paired PTY to its owner even when another host is selected', async () => {
|
||||
const pty = 'remote:host-b@@term_test'
|
||||
mocks.state.ptyIdsByTabId.tab = [pty]
|
||||
mocks.state.terminalLayoutsByTabId.tab = { ptyIdsByLeafId: { [leaf]: pty } }
|
||||
await expect(
|
||||
waitForAntigravityDraftReady('tab', pty, 60000, { activeRuntimeEnvironmentId: 'host-a' })
|
||||
).resolves.toBe(true)
|
||||
expect(mocks.remoteCapability).toHaveBeenCalledWith('host-b', capability, 5000)
|
||||
expect(mocks.call).toHaveBeenCalledWith(
|
||||
{ kind: 'environment', environmentId: 'host-b' },
|
||||
'terminal.wait',
|
||||
{ terminal: 'term_test', for: 'tui-idle', timeoutMs: 60000 },
|
||||
expect.anything()
|
||||
)
|
||||
expect(mocks.capabilities).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not paste on old local or paired hosts', async () => {
|
||||
mocks.capabilities.mockResolvedValue([])
|
||||
mocks.remoteCapability.mockResolvedValue(false)
|
||||
await expect(waitForAntigravityDraftReady('tab', 'pty', 60000, null)).resolves.toBe(false)
|
||||
await expect(
|
||||
waitForAntigravityDraftReady('tab', 'pty', 60000, { activeRuntimeEnvironmentId: 'host-b' })
|
||||
).resolves.toBe(false)
|
||||
expect(mocks.remoteCapability).toHaveBeenCalledWith('host-b', capability, 5000)
|
||||
expect(mocks.call).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ satisfied: false },
|
||||
{ status: 'exited' },
|
||||
{ blockedReason: 'agent-trust-workspace' },
|
||||
{ handle: 'term_other' }
|
||||
])('rejects an unusable wait verdict: %j', async (change) => {
|
||||
mocks.call.mockImplementation(async (_target, method) =>
|
||||
method === 'terminal.resolvePane'
|
||||
? { terminal: { handle: 'term_test', ptyId: 'pty' } }
|
||||
: { wait: { ...ready, ...change } }
|
||||
)
|
||||
await expect(waitForAntigravityDraftReady('tab', 'pty', 60000, null)).resolves.toBe(false)
|
||||
})
|
||||
|
||||
it('rejects a different agent that took over the same PTY while waiting', async () => {
|
||||
mocks.call.mockImplementation(async (_target, method) =>
|
||||
method === 'terminal.wait'
|
||||
? { wait: ready }
|
||||
: {
|
||||
terminal: {
|
||||
handle: 'term_test',
|
||||
ptyId: 'pty',
|
||||
connected: true,
|
||||
writable: true,
|
||||
agentIdentity: 'codex'
|
||||
}
|
||||
}
|
||||
)
|
||||
await expect(waitForAntigravityDraftReady('tab', 'pty', 60000, null)).resolves.toBe(false)
|
||||
})
|
||||
|
||||
it('rejects loss of host contact', async () => {
|
||||
mocks.call.mockRejectedValue(new Error('transport disconnected'))
|
||||
await expect(waitForAntigravityDraftReady('tab', 'pty', 60000, null)).resolves.toBe(false)
|
||||
expect(mocks.call).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('rejects a ready answer after the pane was replaced', async () => {
|
||||
mocks.call.mockImplementation(async (_target, method) => {
|
||||
if (method === 'terminal.resolvePane') {
|
||||
return { terminal: { handle: 'term_test', ptyId: 'pty' } }
|
||||
}
|
||||
mocks.state.ptyIdsByTabId.tab = ['replacement']
|
||||
return { wait: ready }
|
||||
})
|
||||
await expect(waitForAntigravityDraftReady('tab', 'pty', 60000, null)).resolves.toBe(false)
|
||||
})
|
||||
|
||||
it('waits for publication of a newly bound pane without trusting the shell', async () => {
|
||||
mocks.call.mockRejectedValueOnce(new Error('terminal_not_found'))
|
||||
const waiting = waitForAntigravityDraftReady('tab', 'pty', 60000, null)
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
await expect(waiting).resolves.toBe(true)
|
||||
expect(mocks.call).toHaveBeenCalledTimes(4)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,112 @@
|
||||
import type { GlobalSettings } from '../../../shared/global-settings-types'
|
||||
import { ANTIGRAVITY_VISIBLE_READINESS_RUNTIME_CAPABILITY } from '../../../shared/protocol-version'
|
||||
import type {
|
||||
RuntimeTerminalResolvePane,
|
||||
RuntimeTerminalShow,
|
||||
RuntimeTerminalWait
|
||||
} from '../../../shared/runtime-types'
|
||||
import { makePaneKey } from '../../../shared/stable-pane-id'
|
||||
import { toHostSessionTabId } from '../../../shared/terminal-surface-id'
|
||||
import { useAppStore } from '@/store'
|
||||
import { ensureLocalRuntimeCapabilities } from '@/runtime/local-runtime-capabilities'
|
||||
import {
|
||||
callRuntimeRpc,
|
||||
getActiveRuntimeTarget,
|
||||
hasRuntimeRpcErrorCode,
|
||||
runtimeEnvironmentSupportsCapability
|
||||
} from '@/runtime/runtime-rpc-client'
|
||||
import { parseRemoteRuntimePtyId } from '../../../shared/remote-runtime-pty-id'
|
||||
|
||||
/** The host's recorded-screen classifier owns readiness; shell paste mode is not agent input. */
|
||||
export async function waitForAntigravityDraftReady(
|
||||
tabId: string,
|
||||
ptyId: string,
|
||||
timeoutMs: number,
|
||||
settings: Pick<GlobalSettings, 'activeRuntimeEnvironmentId'> | null | undefined
|
||||
): Promise<boolean> {
|
||||
const remotePty = parseRemoteRuntimePtyId(ptyId)
|
||||
const target = remotePty?.environmentId
|
||||
? ({ kind: 'environment', environmentId: remotePty.environmentId } as const)
|
||||
: getActiveRuntimeTarget(settings)
|
||||
const deadline = Date.now() + timeoutMs
|
||||
const stillOwnsPty = (): boolean =>
|
||||
useAppStore.getState().ptyIdsByTabId[tabId]?.includes(ptyId) === true
|
||||
try {
|
||||
const supported =
|
||||
target.kind === 'environment'
|
||||
? await runtimeEnvironmentSupportsCapability(
|
||||
target.environmentId,
|
||||
ANTIGRAVITY_VISIBLE_READINESS_RUNTIME_CAPABILITY,
|
||||
Math.min(timeoutMs, 5000)
|
||||
)
|
||||
: (await ensureLocalRuntimeCapabilities())?.includes(
|
||||
ANTIGRAVITY_VISIBLE_READINESS_RUNTIME_CAPABILITY
|
||||
) === true
|
||||
if (!supported) {
|
||||
return false
|
||||
}
|
||||
while (stillOwnsPty() && Date.now() < deadline) {
|
||||
const layout = useAppStore.getState().terminalLayoutsByTabId[tabId]
|
||||
const leafId = Object.entries(layout?.ptyIdsByLeafId ?? {}).find(
|
||||
([, value]) => value === ptyId
|
||||
)?.[0]
|
||||
if (leafId) {
|
||||
const { terminal } = await callRuntimeRpc<{
|
||||
terminal: RuntimeTerminalResolvePane | null
|
||||
}>(
|
||||
target,
|
||||
'terminal.resolvePane',
|
||||
{ paneKey: makePaneKey(toHostSessionTabId(tabId), leafId) },
|
||||
{ timeoutMs: Math.min(5000, Math.max(1, deadline - Date.now())) }
|
||||
).catch((error: unknown) => {
|
||||
if (hasRuntimeRpcErrorCode(error, 'terminal_not_found')) {
|
||||
return { terminal: null }
|
||||
}
|
||||
throw error
|
||||
})
|
||||
const remoteHandle = remotePty?.handle
|
||||
const samePty = remoteHandle ? terminal?.handle === remoteHandle : terminal?.ptyId === ptyId
|
||||
if (terminal && samePty && stillOwnsPty()) {
|
||||
const remaining = deadline - Date.now()
|
||||
if (remaining <= 0) {
|
||||
return false
|
||||
}
|
||||
const { wait } = await callRuntimeRpc<{ wait: RuntimeTerminalWait }>(
|
||||
target,
|
||||
'terminal.wait',
|
||||
{ terminal: terminal.handle, for: 'tui-idle', timeoutMs: remaining },
|
||||
{ timeoutMs: remaining + 1000 }
|
||||
)
|
||||
if (
|
||||
!stillOwnsPty() ||
|
||||
wait.handle !== terminal.handle ||
|
||||
!wait.satisfied ||
|
||||
wait.status !== 'running' ||
|
||||
wait.blockedReason
|
||||
) {
|
||||
return false
|
||||
}
|
||||
const { terminal: current } = await callRuntimeRpc<{ terminal: RuntimeTerminalShow }>(
|
||||
target,
|
||||
'terminal.show',
|
||||
{ terminal: terminal.handle },
|
||||
{ timeoutMs: 5000 }
|
||||
)
|
||||
return (
|
||||
stillOwnsPty() &&
|
||||
current.handle === terminal.handle &&
|
||||
current.connected &&
|
||||
current.writable &&
|
||||
current.agentIdentity === 'antigravity' &&
|
||||
(remoteHandle ? current.handle === remoteHandle : current.ptyId === ptyId)
|
||||
)
|
||||
}
|
||||
}
|
||||
// The PTY can bind before its pane appears in the host's window graph.
|
||||
await new Promise<void>((resolve) => window.setTimeout(resolve, 100))
|
||||
}
|
||||
} catch {
|
||||
// A disconnected or older host never licenses a best-effort paste.
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -292,7 +292,11 @@ export const ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES = [
|
||||
export const ANTIGRAVITY_CONFIGURED_MODEL_RUNTIME_CAPABILITY =
|
||||
'git.antigravity-configured-model.v1' as const
|
||||
|
||||
export const ANTIGRAVITY_VISIBLE_READINESS_RUNTIME_CAPABILITY =
|
||||
'terminal.antigravity-visible-readiness.v1' as const
|
||||
|
||||
export const RUNTIME_CAPABILITIES = [
|
||||
ANTIGRAVITY_VISIBLE_READINESS_RUNTIME_CAPABILITY,
|
||||
ANTIGRAVITY_CONFIGURED_MODEL_RUNTIME_CAPABILITY,
|
||||
'files.pathsExist',
|
||||
'runtime.status.compat.v1',
|
||||
|
||||
@@ -165,6 +165,7 @@ const TUI_AGENT_CONFIG_SOURCE: Record<TuiAgent, TuiAgentConfigSource> = {
|
||||
},
|
||||
antigravity: {
|
||||
detectCmd: 'agy',
|
||||
draftPasteReadyTimeoutMs: 60_000,
|
||||
promptInjectionMode: 'flag-prompt-interactive'
|
||||
},
|
||||
aider: {
|
||||
|
||||
Reference in New Issue
Block a user