mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(orchestration): start Antigravity workers in a terminal when its chat runtime isn't installed (#33520)
When Antigravity's Orca-managed chat runtime isn't installed, default-chat launches (orchestration workers, agent.launch from CLI/mobile/automations) fall back to a terminal running agy with reason chat_runtime_not_installed instead of failing at terminal_create; an explicit --surface chat is refused with an install hint. Desktop chat tabs still show the install card. Fixes #30973
This commit is contained in:
@@ -68,7 +68,9 @@ const DOWNGRADE_DETAIL: Record<AgentLaunchDowngradeReason, string> = {
|
||||
structured_support_unknown: 'the execution host has not established structured session support',
|
||||
wsl_execution_runtime: 'this workspace runs under WSL',
|
||||
codex_on_windows: 'Codex has no structured session on Windows',
|
||||
structured_unsupported_on_host: 'the execution host cannot create one here'
|
||||
structured_unsupported_on_host: 'the execution host cannot create one here',
|
||||
chat_runtime_not_installed:
|
||||
"its chat runtime isn't installed on this host (install it from a new chat tab in Orca)"
|
||||
}
|
||||
|
||||
const BLOCKER_REASON: Record<StructuredNativeChatBlocker, AgentLaunchDowngradeReason> = {
|
||||
@@ -82,10 +84,14 @@ const BLOCKER_REASON: Record<StructuredNativeChatBlocker, AgentLaunchDowngradeRe
|
||||
'client-capability': 'structured_sessions_unavailable'
|
||||
}
|
||||
|
||||
const HOST_SUPPORT_REASON: Record<'agent' | 'remote' | 'wsl', AgentLaunchDowngradeReason> = {
|
||||
type HostSupportReason = 'agent' | 'remote' | 'wsl' | 'chat-program'
|
||||
type HostLaunchSupport = { supported: boolean; reason?: HostSupportReason }
|
||||
|
||||
const HOST_SUPPORT_REASON: Record<HostSupportReason, AgentLaunchDowngradeReason> = {
|
||||
agent: 'structured_unsupported_on_host',
|
||||
remote: 'remote_execution_host',
|
||||
wsl: 'wsl_execution_runtime'
|
||||
wsl: 'wsl_execution_runtime',
|
||||
'chat-program': 'chat_runtime_not_installed'
|
||||
}
|
||||
|
||||
/** Apply shared feasibility policy to facts available before resolving the workspace. */
|
||||
@@ -195,12 +201,15 @@ async function readStructuredCreateSupport(
|
||||
runtime: Pick<OrcaRuntimeService, 'getStructuredAgentSessionCreateSupport'>,
|
||||
worktreeId: string,
|
||||
agent: TuiAgent | undefined
|
||||
): Promise<{ supported: boolean; reason?: 'agent' | 'remote' | 'wsl' } | null> {
|
||||
): Promise<HostLaunchSupport | null> {
|
||||
if (!agent || !REGISTERED_STRUCTURED_AGENTS.includes(agent)) {
|
||||
return { supported: false, reason: 'agent' }
|
||||
}
|
||||
try {
|
||||
return await runtime.getStructuredAgentSessionCreateSupport(`id:${worktreeId}`, agent)
|
||||
// These launches have no chat tab to offer the install, so they keep the agent's own CLI.
|
||||
return await runtime.getStructuredAgentSessionCreateSupport(`id:${worktreeId}`, agent, {
|
||||
requireInstalledChatProgram: true
|
||||
})
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
@@ -208,7 +217,7 @@ async function readStructuredCreateSupport(
|
||||
|
||||
export function downgradeAgentLaunchModeForHost(
|
||||
receipt: AgentLaunchModeReceipt,
|
||||
support: { supported: boolean; reason?: 'agent' | 'remote' | 'wsl' } | null,
|
||||
support: HostLaunchSupport | null,
|
||||
vocabulary: AgentLaunchModeVocabulary = DEFAULT_LAUNCH_VOCABULARY,
|
||||
context?: AgentLaunchModeSettlement
|
||||
): AgentLaunchModeReceipt {
|
||||
|
||||
@@ -109,7 +109,9 @@ describe('agent.launch with the real floating workspace resolver', () => {
|
||||
|
||||
expect(scope).toHaveBeenCalledExactlyOnceWith(selector)
|
||||
if (structuredPreference) {
|
||||
expect(createSupport).toHaveBeenCalledWith(`id:${FLOATING_TERMINAL_WORKTREE_ID}`, 'codex')
|
||||
expect(createSupport).toHaveBeenCalledWith(`id:${FLOATING_TERMINAL_WORKTREE_ID}`, 'codex', {
|
||||
requireInstalledChatProgram: true
|
||||
})
|
||||
expect(createStructuredSession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ worktree: `id:${FLOATING_TERMINAL_WORKTREE_ID}` })
|
||||
)
|
||||
|
||||
@@ -53,6 +53,29 @@ describe('a worker for a registered native-chat agent', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('starts a terminal worker when the chat program Orca installs is missing here', async () => {
|
||||
const mode = decideWorkerStartMode({
|
||||
params: { agent: 'antigravity' },
|
||||
settings: STRUCTURED_PREFERENCE
|
||||
})
|
||||
// A desktop chat tab can offer the install; a worker cannot, so only it asks for an installed one.
|
||||
const host = {
|
||||
getStructuredAgentSessionCreateSupport: async (
|
||||
_worktree: string,
|
||||
_agent: string,
|
||||
options?: { requireInstalledChatProgram?: boolean }
|
||||
) =>
|
||||
options?.requireInstalledChatProgram
|
||||
? { supported: false, reason: 'chat-program' as const }
|
||||
: { supported: true }
|
||||
}
|
||||
expect(await resolveWorkerStartModeOnHost(host, mode, 'wt-1', 'antigravity')).toMatchObject({
|
||||
mode: 'terminal',
|
||||
preferred: 'structured',
|
||||
reason: 'chat_runtime_not_installed'
|
||||
})
|
||||
})
|
||||
|
||||
it('starts a terminal worker for an agent whose chat provider the user turned off', () => {
|
||||
expect(
|
||||
decideWorkerStartMode({
|
||||
|
||||
@@ -24,7 +24,13 @@ describe('host support evidence', () => {
|
||||
reason: 'structured_support_unknown'
|
||||
})
|
||||
expect(unknown.detail).toContain('has not established')
|
||||
expect(getStructuredAgentSessionCreateSupport).toHaveBeenCalledWith('id:workspace-1', 'claude')
|
||||
expect(getStructuredAgentSessionCreateSupport).toHaveBeenCalledWith(
|
||||
'id:workspace-1',
|
||||
'claude',
|
||||
{
|
||||
requireInstalledChatProgram: true
|
||||
}
|
||||
)
|
||||
getStructuredAgentSessionCreateSupport.mockResolvedValue({ supported: false })
|
||||
const refusal = await resolveWorkerStartModeOnHost(runtime, mode, 'workspace-1', 'claude')
|
||||
expect(refusal.reason).toBe('structured_unsupported_on_host')
|
||||
|
||||
+2
-1
@@ -168,7 +168,8 @@ describe('a fresh worker through the launch executor', () => {
|
||||
expect(f.runtime.getStructuredAgentSessionCreateSupport).toHaveBeenCalledTimes(1)
|
||||
expect(f.runtime.getStructuredAgentSessionCreateSupport).toHaveBeenCalledWith(
|
||||
'id:wt_new',
|
||||
'claude'
|
||||
'claude',
|
||||
{ requireInstalledChatProgram: true }
|
||||
)
|
||||
expect(f.stages).toEqual(['worktree_creating', 'worktree_created', 'terminal_creating'])
|
||||
expect(f.failedStages).toEqual(['worktree_create', 'terminal_create', 'terminal_create'])
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
resolveHostStructuredAgentLaunchEnvironment
|
||||
} from '../structured-agent-launch-support'
|
||||
import { structuredAgentRuntimeMetadata } from '../structured-agent-runtime-metadata'
|
||||
import { structuredAgentChatProgramNeedsInstall } from '../structured-agent-availability'
|
||||
import { structuredLaunchSeedOptions } from '../../../shared/native-chat-session-option-defaults'
|
||||
import { hasPersistedStructuredAgentSessionStore as hasPersistedStructuredAgentSessionStoreOnDisk } from '../structured-agent-session-runtime'
|
||||
import { ensureStructuredAgentSessionHostUnlessRefused } from '../structured-agent-session-host-refusal'
|
||||
@@ -43,16 +44,26 @@ export abstract class OrcaRuntimeWithGetStructuredAgentSessionCreateSupport exte
|
||||
RestoreStructuredAgentSessionTabsOnceMethods['restoreStructuredAgentSessionTabsOnce']
|
||||
>
|
||||
|
||||
/** `requireInstalledChatProgram` is for launches with no chat tab to offer Orca's install. */
|
||||
async getStructuredAgentSessionCreateSupport(
|
||||
worktreeSelector: string,
|
||||
agent: StructuredAgentId
|
||||
): Promise<{ supported: boolean; reason?: 'agent' | 'remote' | 'wsl' }> {
|
||||
return resolveHostStructuredAgentCreateSupport({
|
||||
agent: StructuredAgentId,
|
||||
options?: { requireInstalledChatProgram?: boolean }
|
||||
): Promise<{ supported: boolean; reason?: 'agent' | 'remote' | 'wsl' | 'chat-program' }> {
|
||||
const support = await resolveHostStructuredAgentCreateSupport({
|
||||
agent,
|
||||
worktreeSelector,
|
||||
location: await this.resolveStructuredAgentSessionLocation(worktreeSelector),
|
||||
runtime: this
|
||||
})
|
||||
if (
|
||||
support.supported &&
|
||||
options?.requireInstalledChatProgram &&
|
||||
(await structuredAgentChatProgramNeedsInstall(agent, this))
|
||||
) {
|
||||
return { supported: false, reason: 'chat-program' }
|
||||
}
|
||||
return support
|
||||
}
|
||||
|
||||
/** Use this runtime's registered definition to pin account home; reads/create must not install the host just to inspect it. */
|
||||
|
||||
@@ -12,7 +12,10 @@ import {
|
||||
observeStructuredAgentLaunch,
|
||||
resolveHostStructuredAgentLaunchEnvironment
|
||||
} from './structured-agent-launch-support'
|
||||
import { STRUCTURED_AGENT_RUNTIME_METADATA } from './structured-agent-runtime-metadata'
|
||||
import {
|
||||
STRUCTURED_AGENT_RUNTIME_METADATA,
|
||||
structuredAgentRuntimeMetadata
|
||||
} from './structured-agent-runtime-metadata'
|
||||
import type { StructuredAgentRuntimeMetadata } from './structured-agent-runtime-registration-types'
|
||||
import { recaptureHostShellEnvironment } from './structured-agent-shell-environment'
|
||||
import type { RuntimeStore } from './runtime-store-contract'
|
||||
@@ -89,6 +92,18 @@ async function observeChatProgram(
|
||||
return chatProgram.installable?.() ? 'installable' : 'missing'
|
||||
}
|
||||
|
||||
/** True when a chat start would fail until Orca installs the agent's chat program on this host. */
|
||||
export async function structuredAgentChatProgramNeedsInstall(
|
||||
agent: string,
|
||||
runtime: HealthRuntime
|
||||
): Promise<boolean> {
|
||||
const metadata = structuredAgentRuntimeMetadata(agent)
|
||||
return Boolean(
|
||||
metadata?.chatProgram?.installable &&
|
||||
(await observeChatProgram(metadata, runtime)) === 'installable'
|
||||
)
|
||||
}
|
||||
|
||||
/** Refresh shell facts on install, settings or agent Refresh; retain health without readers for immediate later subscription. */
|
||||
export function installStructuredAgentAvailability(
|
||||
store: Pick<RuntimeStore, 'onSettingsChanged'> | null,
|
||||
|
||||
@@ -53,7 +53,7 @@ type TestLocation = {
|
||||
|
||||
type SupportResult = {
|
||||
supported: boolean
|
||||
reason?: 'agent' | 'remote' | 'wsl'
|
||||
reason?: 'agent' | 'remote' | 'wsl' | 'chat-program'
|
||||
}
|
||||
|
||||
function createRuntime(location: TestLocation): OrcaRuntimeService {
|
||||
|
||||
@@ -137,6 +137,7 @@ export type AgentLaunchModeReason =
|
||||
| 'wsl_execution_runtime'
|
||||
| 'codex_on_windows'
|
||||
| 'structured_unsupported_on_host'
|
||||
| 'chat_runtime_not_installed'
|
||||
|
||||
/** Restates `WorkerStartModeReceipt` in surface-neutral terms so orchestration's receipt and a
|
||||
* mobile or renderer launch report the same vocabulary. */
|
||||
|
||||
Reference in New Issue
Block a user