mirror of
https://github.com/stablyai/orca.git
synced 2026-10-02 00:02:05 +00:00
fix(native-chat): route structured sessions by execution host
This commit is contained in:
@@ -3121,7 +3121,10 @@ function TerminalPane(
|
||||
structuredSessionAgent,
|
||||
terminalAgent: chatPaneResolvedAgent ?? chatPaneLaunchAgent
|
||||
})
|
||||
const structuredChatTarget = useMemo(() => ({ kind: 'local' as const }), [])
|
||||
const structuredChatTarget = useMemo(() => {
|
||||
const environmentId = getRuntimeEnvironmentIdForWorktree(useAppStore.getState(), worktreeId)
|
||||
return environmentId ? { kind: 'environment' as const, environmentId } : { kind: 'local' as const }
|
||||
}, [worktreeId])
|
||||
// The launch draft is keyed by tab, so gate it on the same pane ownership the
|
||||
// launch agent uses: a split sibling must not inherit the seeded text.
|
||||
const chatPaneOwnsTabWideLaunchDraft = nativeChatLeafOwnsTabWideEvidence({
|
||||
|
||||
@@ -16,11 +16,18 @@ 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'
|
||||
|
||||
function newSessionId(agent: AgentSessionHandleProvider): string {
|
||||
return `${agent}_${crypto.randomUUID().replaceAll('-', '_')}`
|
||||
}
|
||||
|
||||
function targetForWorktree(worktreeId: string): RuntimeClientTarget {
|
||||
const environmentId = getRuntimeEnvironmentIdForWorktree(useAppStore.getState(), worktreeId)
|
||||
return environmentId ? { kind: 'environment', environmentId } : { kind: 'local' }
|
||||
}
|
||||
|
||||
export async function launchStructuredAgentSession(
|
||||
worktreeId: string,
|
||||
agent: AgentSessionHandleProvider
|
||||
@@ -28,8 +35,12 @@ export async function launchStructuredAgentSession(
|
||||
const sessionId = newSessionId(agent)
|
||||
const fields = { worktree: toRuntimeWorktreeSelector(worktreeId), agent }
|
||||
const state = useAppStore.getState()
|
||||
const target = targetForWorktree(worktreeId)
|
||||
recordWebSessionFocusIntent(
|
||||
{ environmentId: LOCAL_STRUCTURED_SESSION_OWNER },
|
||||
{
|
||||
environmentId:
|
||||
target.kind === 'environment' ? target.environmentId : LOCAL_STRUCTURED_SESSION_OWNER
|
||||
},
|
||||
worktreeId,
|
||||
`agent-session:${sessionId}`,
|
||||
undefined,
|
||||
@@ -38,7 +49,7 @@ export async function launchStructuredAgentSession(
|
||||
try {
|
||||
const result = await callStructuredAgentSession<
|
||||
AgentSessionMutationResult<AgentSessionAttachResult>
|
||||
>({ kind: 'local' }, 'agentSession.create', {
|
||||
>(target, 'agentSession.create', {
|
||||
envelope: {
|
||||
sessionId,
|
||||
clientOperationId: createStructuredAgentSessionOperationId(() => crypto.randomUUID()),
|
||||
@@ -59,7 +70,10 @@ export async function launchStructuredAgentSession(
|
||||
// A concurrent create may have replaced this intent. Only clear the failed
|
||||
// session's slot; never erase a later successful create's focus request.
|
||||
clearWebSessionFocusIntentIfMatches(
|
||||
{ environmentId: LOCAL_STRUCTURED_SESSION_OWNER },
|
||||
{
|
||||
environmentId:
|
||||
target.kind === 'environment' ? target.environmentId : LOCAL_STRUCTURED_SESSION_OWNER
|
||||
},
|
||||
worktreeId,
|
||||
`agent-session:${sessionId}`
|
||||
)
|
||||
|
||||
@@ -12,10 +12,14 @@ import { refreshLocalStructuredSessionTabs } from '@/runtime/local-structured-se
|
||||
import { callStructuredAgentSession } from '@/runtime/structured-agent-session-client'
|
||||
import type { RuntimeMobileSessionTabsResult } from '../../../shared/runtime-types'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { getRuntimeEnvironmentIdForWorktree } from '@/lib/worktree-runtime-owner'
|
||||
import type { RuntimeClientTarget } from '@/runtime/runtime-client-target'
|
||||
import { useAppStore } from '@/store'
|
||||
|
||||
type StructuredLaunchState = {
|
||||
promise: Promise<string>
|
||||
sessionId?: string
|
||||
target: RuntimeClientTarget
|
||||
visibilityUnknown: boolean
|
||||
}
|
||||
|
||||
@@ -48,8 +52,19 @@ function trackLaunchSettlement(
|
||||
)
|
||||
}
|
||||
|
||||
async function verifyPublishedSession(worktreeId: string, sessionId: string): Promise<string> {
|
||||
const snapshots = await refreshLocalStructuredSessionTabs()
|
||||
async function verifyPublishedSession(
|
||||
worktreeId: string,
|
||||
sessionId: string,
|
||||
target: RuntimeClientTarget
|
||||
): Promise<string> {
|
||||
const snapshots =
|
||||
target.kind === 'local'
|
||||
? await refreshLocalStructuredSessionTabs()
|
||||
: await callStructuredAgentSession<{ snapshots?: RuntimeMobileSessionTabsResult[] }>(
|
||||
target,
|
||||
'session.tabs.listAll',
|
||||
{}
|
||||
).then((result) => result.snapshots ?? [])
|
||||
const published = snapshots.some(
|
||||
(snapshot) =>
|
||||
snapshot.worktree === worktreeId &&
|
||||
@@ -70,7 +85,11 @@ function launchStructuredAgentSessionOnce(
|
||||
if (existing) {
|
||||
if (existing.visibilityUnknown && existing.sessionId) {
|
||||
existing.visibilityUnknown = false
|
||||
existing.promise = verifyPublishedSession(worktreeId, existing.sessionId).catch((error) => {
|
||||
existing.promise = verifyPublishedSession(
|
||||
worktreeId,
|
||||
existing.sessionId,
|
||||
existing.target
|
||||
).catch((error) => {
|
||||
existing.visibilityUnknown = true
|
||||
throw error
|
||||
})
|
||||
@@ -83,12 +102,16 @@ function launchStructuredAgentSessionOnce(
|
||||
// renderer; clearing here lets a rapid second click create a sibling chat.
|
||||
const state: StructuredLaunchState = {
|
||||
promise: Promise.resolve(''),
|
||||
target: (() => {
|
||||
const environmentId = getRuntimeEnvironmentIdForWorktree(useAppStore.getState(), worktreeId)
|
||||
return environmentId ? { kind: 'environment', environmentId } : { kind: 'local' }
|
||||
})(),
|
||||
visibilityUnknown: false
|
||||
}
|
||||
state.promise = launchStructuredAgentSession(worktreeId, agent)
|
||||
.then((sessionId) => {
|
||||
state.sessionId = sessionId
|
||||
return verifyPublishedSession(worktreeId, sessionId)
|
||||
return verifyPublishedSession(worktreeId, sessionId, state.target)
|
||||
})
|
||||
.catch((error) => {
|
||||
if (state.sessionId) {
|
||||
|
||||
@@ -4,6 +4,7 @@ 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 {
|
||||
@@ -30,6 +31,13 @@ export function canUseStructuredNativeChat(state: AppState, worktreeId: string):
|
||||
: 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 —
|
||||
// windows-host, WSL, and keys that resolve no project runtime (folder
|
||||
// workspaces, floating terminal) — so create cannot fail after the click.
|
||||
if (getRendererAppPlatform() === 'win32') {
|
||||
return false
|
||||
}
|
||||
// Refuse WSL and repair-required runtimes; Windows native execution is
|
||||
// supported when the host advertises the process identity capability.
|
||||
const projectRuntime = getLocalProjectExecutionRuntimeContext(state, worktreeId)
|
||||
|
||||
Reference in New Issue
Block a user