mirror of
https://github.com/stablyai/orca.git
synced 2026-10-01 16:02:03 +00:00
feat(remote): route structured native chat to paired hosts
This commit is contained in:
@@ -16,6 +16,8 @@ import {
|
||||
resolveWebSessionVisibleTabId
|
||||
} from '@/runtime/web-session-focus-intent'
|
||||
import { LOCAL_STRUCTURED_SESSION_OWNER } from '@/runtime/local-structured-session-tabs-sync'
|
||||
import { getRuntimeEnvironmentIdForWorktree } from '@/lib/worktree-runtime-owner'
|
||||
import type { RuntimeClientTarget } from '@/runtime/runtime-client-target'
|
||||
|
||||
type StructuredAgentSessionCreateParams = {
|
||||
envelope: AgentSessionMutationEnvelope
|
||||
@@ -26,6 +28,8 @@ type StructuredAgentSessionCreateParams = {
|
||||
export type StructuredAgentSessionLaunchIntent = {
|
||||
sessionId: string
|
||||
worktreeId: string
|
||||
/** Execution host selected when the intent was created (old callers omit it). */
|
||||
target?: RuntimeClientTarget
|
||||
params: StructuredAgentSessionCreateParams
|
||||
}
|
||||
|
||||
@@ -37,8 +41,12 @@ export function createStructuredCodexSessionLaunchIntent(
|
||||
const sessionId = `codex_${crypto.randomUUID().replaceAll('-', '_')}`
|
||||
const fields = { worktree: toRuntimeWorktreeSelector(worktreeId), agent: 'codex' as const }
|
||||
const state = useAppStore.getState()
|
||||
const environmentId = getRuntimeEnvironmentIdForWorktree(state, worktreeId)
|
||||
const target: RuntimeClientTarget = environmentId
|
||||
? { kind: 'environment', environmentId }
|
||||
: { kind: 'local' }
|
||||
recordWebSessionFocusIntent(
|
||||
{ environmentId: LOCAL_STRUCTURED_SESSION_OWNER },
|
||||
{ environmentId: environmentId ?? LOCAL_STRUCTURED_SESSION_OWNER },
|
||||
worktreeId,
|
||||
`agent-session:${sessionId}`,
|
||||
undefined,
|
||||
@@ -47,6 +55,7 @@ export function createStructuredCodexSessionLaunchIntent(
|
||||
return {
|
||||
sessionId,
|
||||
worktreeId,
|
||||
target,
|
||||
params: {
|
||||
envelope: {
|
||||
sessionId,
|
||||
@@ -67,7 +76,12 @@ export function abandonStructuredAgentSessionLaunchIntent(
|
||||
intent: StructuredAgentSessionLaunchIntent
|
||||
): void {
|
||||
clearWebSessionFocusIntentIfMatches(
|
||||
{ environmentId: LOCAL_STRUCTURED_SESSION_OWNER },
|
||||
{
|
||||
environmentId:
|
||||
intent.target?.kind === 'environment'
|
||||
? intent.target.environmentId
|
||||
: LOCAL_STRUCTURED_SESSION_OWNER
|
||||
},
|
||||
intent.worktreeId,
|
||||
`agent-session:${intent.sessionId}`
|
||||
)
|
||||
@@ -78,7 +92,7 @@ export async function launchStructuredCodexSession(
|
||||
): Promise<string> {
|
||||
const result = await callStructuredAgentSession<
|
||||
AgentSessionMutationResult<AgentSessionAttachResult>
|
||||
>({ kind: 'local' }, 'agentSession.create', intent.params)
|
||||
>(intent.target ?? { kind: 'local' }, 'agentSession.create', intent.params)
|
||||
if (!result.ok) {
|
||||
abandonStructuredAgentSessionLaunchIntent(intent)
|
||||
throw new StructuredAgentSessionCreateRefusalError(result.refusal.message)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { toast } from 'sonner'
|
||||
import { launchStructuredAgentSession } from '@/lib/launch-structured-agent-session'
|
||||
import { refreshLocalStructuredSessionTabs } from '@/runtime/local-structured-session-tabs-sync'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { AgentSessionHandleProvider } from '../../../shared/agent-session-provider-handle'
|
||||
import { agentSessionProviderLabel } from '../../../shared/agent-session-provider-label'
|
||||
import {
|
||||
@@ -10,6 +8,10 @@ import {
|
||||
StructuredAgentSessionCreateRefusalError,
|
||||
type StructuredAgentSessionLaunchIntent
|
||||
} from '@/lib/launch-structured-codex-session'
|
||||
import { refreshLocalStructuredSessionTabs } from '@/runtime/local-structured-session-tabs-sync'
|
||||
import { callStructuredAgentSession } from '@/runtime/structured-agent-session-client'
|
||||
import type { RuntimeMobileSessionTabsResult } from '../../../shared/runtime-types'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
|
||||
type StructuredLaunchState = {
|
||||
promise: Promise<string>
|
||||
@@ -150,7 +152,15 @@ const pendingCodexLaunches = new Map<
|
||||
>()
|
||||
|
||||
async function verifyCodexPublished(intent: StructuredAgentSessionLaunchIntent): Promise<string> {
|
||||
const snapshots = await refreshLocalStructuredSessionTabs()
|
||||
const target = intent.target ?? { kind: 'local' as const }
|
||||
const snapshots =
|
||||
target.kind === 'local'
|
||||
? await refreshLocalStructuredSessionTabs()
|
||||
: await callStructuredAgentSession<{ snapshots?: RuntimeMobileSessionTabsResult[] }>(
|
||||
target,
|
||||
'session.tabs.listAll',
|
||||
{}
|
||||
).then((result) => result.snapshots ?? [])
|
||||
if (
|
||||
!snapshots.some(
|
||||
(snapshot) =>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import type { AppState } from '@/store/types'
|
||||
import { getLocalProjectExecutionRuntimeContext } from '@/lib/local-preflight-context'
|
||||
import { getExecutionHostIdForWorktree } from '@/lib/worktree-runtime-owner'
|
||||
import {
|
||||
getExecutionHostIdForWorktree,
|
||||
getRuntimeEnvironmentIdForWorktree
|
||||
} from '@/lib/worktree-runtime-owner'
|
||||
import { getRendererAppPlatform } from '@/lib/renderer-app-platform'
|
||||
import { STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY } from '../../../shared/protocol-version'
|
||||
|
||||
export function canUseStructuredNativeChat(state: AppState, worktreeId: string): boolean {
|
||||
if (state.settings?.experimentalStructuredNativeChat !== true) {
|
||||
@@ -13,8 +17,19 @@ export function canUseStructuredNativeChat(state: AppState, worktreeId: string):
|
||||
if (state.settings?.openAgentTabsInChatByDefault !== true) {
|
||||
return false
|
||||
}
|
||||
if (getExecutionHostIdForWorktree(state, worktreeId) !== 'local') {
|
||||
return false
|
||||
const executionHostId = getExecutionHostIdForWorktree(state, worktreeId)
|
||||
if (executionHostId !== 'local') {
|
||||
// Runtime-owned worktrees may use the paired structured host when its
|
||||
// negotiated status is already known. SSH ownership deliberately remains
|
||||
// on the legacy terminal bridge: it has no agent-session RPC transport.
|
||||
if (!executionHostId.startsWith('runtime:')) {
|
||||
return false
|
||||
}
|
||||
const environmentId = getRuntimeEnvironmentIdForWorktree(state, worktreeId)
|
||||
const status = environmentId
|
||||
? state.runtimeStatusByEnvironmentId?.get(environmentId)?.status
|
||||
: undefined
|
||||
return status?.capabilities?.includes(STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY) === true
|
||||
}
|
||||
// The shipped Windows process-tree addon may not expose creation time. Until
|
||||
// the host advertises that proof, refuse every local Windows execution path —
|
||||
|
||||
@@ -166,6 +166,8 @@ export const AUTOMATION_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY =
|
||||
export const NATIVE_REMOTE_RUNTIME_CLIENT_CAPABILITIES = [
|
||||
SESSION_TAB_CLOSE_INTENT_RUNTIME_CAPABILITY,
|
||||
AGENT_SESSION_BOUNDARY_RUNTIME_CAPABILITY,
|
||||
STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY,
|
||||
STRUCTURED_AGENT_SESSION_HOLD_RUNTIME_CAPABILITY,
|
||||
WORKTREE_VISIBILITY_DEFAULTS_RUNTIME_CAPABILITY,
|
||||
WORKTREE_VISIBILITY_SOURCE_DEFAULTS_RUNTIME_CAPABILITY,
|
||||
WORKTREE_GITHUB_PR_SUPPRESSION_RUNTIME_CAPABILITY,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import {
|
||||
AGENT_SESSION_BOUNDARY_RUNTIME_CAPABILITY,
|
||||
STRUCTURED_AGENT_SESSION_HOLD_RUNTIME_CAPABILITY,
|
||||
STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY,
|
||||
AUTOMATION_OWNER_FENCING_RUNTIME_CAPABILITY,
|
||||
SESSION_TAB_CLOSE_INTENT_RUNTIME_CAPABILITY,
|
||||
SESSION_TABS_AUTHORITATIVE_INVENTORY_RUNTIME_CAPABILITY,
|
||||
@@ -19,6 +21,10 @@ export function remoteRuntimeClientCapabilities(
|
||||
SESSION_TAB_CLOSE_INTENT_RUNTIME_CAPABILITY,
|
||||
SESSION_TABS_AUTHORITATIVE_INVENTORY_RUNTIME_CAPABILITY,
|
||||
AGENT_SESSION_BOUNDARY_RUNTIME_CAPABILITY,
|
||||
// Paired runtimes only expose the structured agent-session surface when
|
||||
// the client explicitly proves it can render and drive those tabs.
|
||||
STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY,
|
||||
STRUCTURED_AGENT_SESSION_HOLD_RUNTIME_CAPABILITY,
|
||||
SKILL_INSTALL_RESULT_V2_CAPABILITY,
|
||||
WORKTREE_GITHUB_PR_SUPPRESSION_RUNTIME_CAPABILITY,
|
||||
WORKTREE_VISIBILITY_DEFAULTS_RUNTIME_CAPABILITY,
|
||||
|
||||
@@ -95,6 +95,22 @@ export function getCleanupRequest(
|
||||
params: { subscriptionId: subscription.requestId }
|
||||
}
|
||||
}
|
||||
// Structured agent sessions use a per-request subscription id so several
|
||||
// surfaces can watch one session over a shared relay without evicting each
|
||||
// other. Replay the exact id on close to release the host hold.
|
||||
if (subscription.method === 'agentSession.subscribe') {
|
||||
const sessionId =
|
||||
typeof subscription.params === 'object' && subscription.params !== null
|
||||
? (subscription.params as { sessionId?: unknown }).sessionId
|
||||
: undefined
|
||||
if (typeof sessionId !== 'string' || sessionId.length === 0) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
method: 'agentSession.unsubscribe',
|
||||
params: { sessionId, subscriptionId: subscription.requestId }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user