mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 16:03:49 +00:00
138 lines
5.3 KiB
TypeScript
138 lines
5.3 KiB
TypeScript
import { toast } from 'sonner'
|
|
import { useAppStore } from '@/store'
|
|
import { buildAgentResumeStartupPlan } from '@/lib/tui-agent-startup'
|
|
import { tuiAgentToAgentKind } from '@/lib/telemetry'
|
|
import { reconcileTabOrder } from '@/components/tab-bar/reconcile-order'
|
|
import {
|
|
resolveAgentResumeLaunchTarget,
|
|
type AgentResumeLaunchTarget
|
|
} from '@/lib/agent-resume-launch-target'
|
|
import { getExecutionHostIdForWorktree } from '@/lib/worktree-runtime-owner'
|
|
import { getLocalProjectExecutionRuntimeContext } from '@/lib/local-preflight-context'
|
|
import {
|
|
resolveTuiAgentLaunchArgs,
|
|
resolveTuiAgentLaunchEnv
|
|
} from '../../../shared/tui-agent-launch-defaults'
|
|
import type { SleepingAgentSessionRecord } from '../../../shared/agent-session-resume'
|
|
import { translate } from '@/i18n/i18n'
|
|
|
|
export type ResumeSleepingAgentSessionsOptions = {
|
|
suppressNavigation?: boolean
|
|
/** Provider-session claim keys already woken in place by mounted panes
|
|
* (WAKE_HIBERNATED_AGENTS_WORKTREE_EVENT). Their sleeping records are
|
|
* cleared only after the in-place spawn succeeds, so the generic resume
|
|
* must neither launch nor clear them here. */
|
|
skipClaimKeys?: ReadonlySet<string>
|
|
/** Called with the tab id of each freshly launched resume tab, so
|
|
* navigation-suppressed callers can background-mount exactly those tabs. */
|
|
onSessionLaunched?: (tabId: string) => void
|
|
}
|
|
|
|
function getResumeLaunchTarget(worktreeId: string): AgentResumeLaunchTarget {
|
|
const state = useAppStore.getState()
|
|
const worktree = state.getKnownWorktreeById(worktreeId)
|
|
const repo = worktree ? state.repos.find((entry) => entry.id === worktree.repoId) : null
|
|
// The resume tab is created without a shell override, so the global Windows shell wins.
|
|
return resolveAgentResumeLaunchTarget({
|
|
projectRuntime: getLocalProjectExecutionRuntimeContext(state, worktreeId),
|
|
connectionId: repo?.connectionId,
|
|
executionHostId: getExecutionHostIdForWorktree(state, worktreeId),
|
|
worktreePath: worktree?.path,
|
|
terminalWindowsShell: state.settings?.terminalWindowsShell
|
|
})
|
|
}
|
|
|
|
function appendTabToWorktreeOrder(worktreeId: string, tabId: string): void {
|
|
const state = useAppStore.getState()
|
|
const termIds = (state.tabsByWorktree[worktreeId] ?? []).map((tab) => tab.id)
|
|
const editorIds = state.openFiles
|
|
.filter((file) => file.worktreeId === worktreeId)
|
|
.map((f) => f.id)
|
|
const browserIds = (state.browserTabsByWorktree?.[worktreeId] ?? []).map((tab) => tab.id)
|
|
const base = reconcileTabOrder(
|
|
state.tabBarOrderByWorktree[worktreeId],
|
|
termIds,
|
|
editorIds,
|
|
browserIds
|
|
)
|
|
const order = base.filter((id) => id !== tabId)
|
|
order.push(tabId)
|
|
state.setTabBarOrder(worktreeId, order)
|
|
}
|
|
|
|
// Why: mobile-driven wake runs on the desktop host renderer, so it must create
|
|
// the resume tab without stealing the desktop's active worktree/tab/view.
|
|
export function launchSleepingAgentSession(
|
|
record: SleepingAgentSessionRecord,
|
|
options?: ResumeSleepingAgentSessionsOptions
|
|
): boolean {
|
|
const state = useAppStore.getState()
|
|
const launchConfig = record.launchConfig
|
|
const resumeTarget = getResumeLaunchTarget(record.worktreeId)
|
|
const startupPlan = buildAgentResumeStartupPlan({
|
|
agent: record.agent,
|
|
providerSession: record.providerSession,
|
|
cmdOverrides: state.settings?.agentCmdOverrides ?? {},
|
|
agentArgs:
|
|
launchConfig !== undefined
|
|
? launchConfig.agentArgs
|
|
: resolveTuiAgentLaunchArgs(record.agent, state.settings?.agentDefaultArgs),
|
|
agentEnv:
|
|
launchConfig !== undefined
|
|
? launchConfig.agentEnv
|
|
: resolveTuiAgentLaunchEnv(record.agent, state.settings?.agentDefaultEnv),
|
|
...(launchConfig?.agentCommand ? { agentCommand: launchConfig.agentCommand } : {}),
|
|
...(launchConfig?.ompResumeFilePath
|
|
? { ompResumeFilePath: launchConfig.ompResumeFilePath }
|
|
: {}),
|
|
platform: resumeTarget.platform,
|
|
shell: resumeTarget.shell
|
|
})
|
|
if (!startupPlan) {
|
|
toast.error(
|
|
translate(
|
|
'auto.lib.resume.sleeping.agent.session.f235f604fd',
|
|
'This agent session cannot be resumed.'
|
|
)
|
|
)
|
|
return false
|
|
}
|
|
|
|
const tab = state.createTab(record.worktreeId, undefined, undefined, {
|
|
launchAgent: record.agent,
|
|
...(options?.suppressNavigation ? { activate: false, recordInteraction: false } : {})
|
|
})
|
|
state.queueTabStartupCommand(tab.id, {
|
|
command: startupPlan.launchCommand,
|
|
...(startupPlan.env ? { env: startupPlan.env } : {}),
|
|
launchConfig: startupPlan.launchConfig,
|
|
resumeProviderSession: record.providerSession,
|
|
launchAgent: record.agent,
|
|
...(launchConfig ? { agentArgsOverride: launchConfig.agentArgs } : {}),
|
|
sleepingAgentResumeIdentity: {
|
|
paneKey: record.paneKey,
|
|
capturedAt: record.capturedAt
|
|
},
|
|
...(startupPlan.startupCommandDelivery
|
|
? { startupCommandDelivery: startupPlan.startupCommandDelivery }
|
|
: {}),
|
|
showSessionRestoredBanner: true,
|
|
telemetry: {
|
|
agent_kind: tuiAgentToAgentKind(record.agent),
|
|
launch_source: 'sidebar',
|
|
request_kind: 'resume'
|
|
}
|
|
})
|
|
state.claimAutomaticAgentResume(tab.id, {
|
|
worktreeId: record.worktreeId,
|
|
launchAgent: record.agent,
|
|
providerSession: record.providerSession
|
|
})
|
|
if (!options?.suppressNavigation) {
|
|
state.setActiveTabType('terminal')
|
|
}
|
|
appendTabToWorktreeOrder(record.worktreeId, tab.id)
|
|
options?.onSessionLaunched?.(tab.id)
|
|
return true
|
|
}
|