From a574afffdd23c5f2ca1d91f3b1e2501c69d18b77 Mon Sep 17 00:00:00 2001 From: cong Date: Sat, 11 Apr 2026 18:37:51 +0800 Subject: [PATCH] feat(web): add auto-spawn session from URL params Support spawning a session via URL parameters (?spawn=true&machine=xxx&dir=/path&boot=message). The spawn params are captured at module load time before URL cleanup, then the hook calls spawnSession API and navigates to the new session automatically. Co-Authored-By: Claude Opus 4.6 --- web/src/App.tsx | 13 +++++++--- web/src/hooks/useAutoSpawn.ts | 46 +++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 web/src/hooks/useAutoSpawn.ts diff --git a/web/src/App.tsx b/web/src/App.tsx index 7ab0798c2..a92a0c397 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -11,6 +11,7 @@ import { useSyncingState } from '@/hooks/useSyncingState' import { usePushNotifications } from '@/hooks/usePushNotifications' import { useViewportHeight } from '@/hooks/useViewportHeight' import { useVisibilityReporter } from '@/hooks/useVisibilityReporter' +import { useAutoSpawn } from '@/hooks/useAutoSpawn' import { queryKeys } from '@/lib/query-keys' import { AppContextProvider } from '@/lib/app-context' import { fetchLatestMessages } from '@/lib/message-window-store' @@ -143,12 +144,13 @@ function AppInner() { if (!token || !api) return const { pathname, search, hash, state } = router.history.location const searchParams = new URLSearchParams(search) - if (!searchParams.has('server') && !searchParams.has('hub') && !searchParams.has('token')) { + const authKeys = ['server', 'hub', 'token', 'spawn', 'machine', 'dir', 'boot'] + if (!authKeys.some(k => searchParams.has(k))) { return } - searchParams.delete('server') - searchParams.delete('hub') - searchParams.delete('token') + for (const key of authKeys) { + searchParams.delete(key) + } const nextSearch = searchParams.toString() const nextHref = `${pathname}${nextSearch ? `?${nextSearch}` : ''}${hash}` router.history.replace(nextHref, state) @@ -246,6 +248,9 @@ function AppInner() { return { all: true } }, [selectedSessionId]) + // Auto-spawn session from URL params (?spawn=true&machine=xxx&dir=/path) + useAutoSpawn(api) + const { subscriptionId } = useSSE({ enabled: Boolean(api && token), token: token ?? '', diff --git a/web/src/hooks/useAutoSpawn.ts b/web/src/hooks/useAutoSpawn.ts new file mode 100644 index 000000000..1a74154c0 --- /dev/null +++ b/web/src/hooks/useAutoSpawn.ts @@ -0,0 +1,46 @@ +import { useEffect, useRef } from 'react' +import { useNavigate } from '@tanstack/react-router' +import type { ApiClient } from '@/api/client' + +// Capture spawn params at module load time, before any effect can clean the URL +const initialSearch = typeof window !== 'undefined' ? window.location.search : '' +const initialQuery = new URLSearchParams(initialSearch) +const SPAWN_PARAMS = { + spawn: initialQuery.get('spawn') === 'true', + machine: initialQuery.get('machine'), + dir: initialQuery.get('dir'), + boot: initialQuery.get('boot'), +} as const + +export function useAutoSpawn(api: ApiClient | null) { + const navigate = useNavigate() + const attemptedRef = useRef(false) + + useEffect(() => { + if (!api || attemptedRef.current) return + if (!SPAWN_PARAMS.spawn || !SPAWN_PARAMS.machine || !SPAWN_PARAMS.dir) return + + attemptedRef.current = true + + api.spawnSession(SPAWN_PARAMS.machine, SPAWN_PARAMS.dir).then(async (result) => { + if (result.type === 'success') { + if (SPAWN_PARAMS.boot) { + try { + await api.sendMessage(result.sessionId, SPAWN_PARAMS.boot) + } catch (err) { + console.error('Auto-spawn boot message failed:', err) + } + } + navigate({ + to: '/sessions/$sessionId', + params: { sessionId: result.sessionId }, + replace: true, + }) + } else { + console.error('Auto-spawn failed:', result.message) + } + }).catch((err) => { + console.error('Auto-spawn error:', err) + }) + }, [api, navigate]) +}