@athenaintel/react 0.38.1 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -2
- package/dist/chat-ui/thread/statewire-history-edge.cjs +13 -0
- package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
- package/dist/chat-ui/thread/statewire-history-edge.js +13 -0
- package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
- package/dist/diagnostics/errors.cjs +5 -0
- package/dist/diagnostics/errors.cjs.map +1 -1
- package/dist/diagnostics/errors.d.ts +5 -0
- package/dist/diagnostics/errors.js +5 -0
- package/dist/diagnostics/errors.js.map +1 -1
- package/dist/index.cjs +14 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +5 -2
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.js +5 -2
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/provider/statewire-history-context.cjs +48 -0
- package/dist/provider/statewire-history-context.cjs.map +1 -0
- package/dist/provider/statewire-history-context.d.ts +36 -0
- package/dist/provider/statewire-history-context.js +48 -0
- package/dist/provider/statewire-history-context.js.map +1 -0
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/styles.css +1 -1
- package/dist/workbench/ComputerLiveDesktop.cjs +287 -0
- package/dist/workbench/ComputerLiveDesktop.cjs.map +1 -0
- package/dist/workbench/ComputerLiveDesktop.d.ts +27 -0
- package/dist/workbench/ComputerLiveDesktop.js +287 -0
- package/dist/workbench/ComputerLiveDesktop.js.map +1 -0
- package/dist/workbench/SessionWorkbench.cjs +2 -2
- package/dist/workbench/SessionWorkbench.cjs.map +1 -1
- package/dist/workbench/SessionWorkbench.js +2 -2
- package/dist/workbench/SessionWorkbench.js.map +1 -1
- package/dist/workbench/live-desktop-api.cjs +139 -0
- package/dist/workbench/live-desktop-api.cjs.map +1 -0
- package/dist/workbench/live-desktop-api.d.ts +69 -0
- package/dist/workbench/live-desktop-api.js +139 -0
- package/dist/workbench/live-desktop-api.js.map +1 -0
- package/dist/workbench/session-workbench-context.cjs.map +1 -1
- package/dist/workbench/session-workbench-context.d.ts +7 -0
- package/dist/workbench/session-workbench-context.js.map +1 -1
- package/dist/workbench/session-workbench-panes.cjs +38 -2
- package/dist/workbench/session-workbench-panes.cjs.map +1 -1
- package/dist/workbench/session-workbench-panes.js +38 -2
- package/dist/workbench/session-workbench-panes.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ComputerLiveDesktop.js","sources":["../../src/workbench/ComputerLiveDesktop.tsx"],"sourcesContent":["\"use client\";\n\nimport { useMutation } from '@tanstack/react-query';\nimport {\n AlertCircleIcon,\n EyeIcon,\n LoaderIcon,\n MonitorOffIcon,\n MousePointer2Icon,\n PlayIcon,\n RefreshCwIcon,\n XIcon,\n} from 'lucide-react';\nimport { type FC, type ReactNode, useContext, useEffect, useRef, useState } from 'react';\nimport { useAthenaQueryClient } from '../lib/use-athena-rest-query';\nimport { cn } from '../lib/utils';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport {\n createComputerLiveDesktopSession,\n finalLiveDesktopCloseNotice,\n LiveDesktopMode,\n type LiveDesktopSession,\n liveDesktopViewerUrl,\n readLiveDesktopViewerMessage,\n} from './live-desktop-api';\nimport { SessionWorkbenchContext } from './session-workbench-context';\nimport { useAthenaComputerStatus } from './use-session-workspace';\n\n/** How long a viewer may take to show video before the pane calls it failed. */\nexport const LIVE_DESKTOP_READY_TIMEOUT_MS = 30_000;\n\nconst RUNNING_STATES: ReadonlySet<string> = new Set(['started', 'running', 'ready']);\n\nconst BUTTON_CLASS =\n 'inline-flex shrink-0 items-center gap-1 rounded-md border border-border bg-background px-2 py-1 font-medium text-xs transition-colors hover:bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring/60 disabled:opacity-60';\n\n/** The viewer page the pane frames: one per ticket, its own messages move it on. */\ninterface MountedViewer {\n url: string;\n mode: LiveDesktopMode;\n display: { width: number; height: number } | null;\n /** `connected` once the page reports video (`ready`); never on a socket alone. */\n state: 'connecting' | 'connected';\n}\n\n/** Why the pane shows no viewer, when it has something to say. */\ntype Notice =\n | { kind: 'display_not_running'; text: string }\n | { kind: 'unsupported'; text: string }\n | { kind: 'ended'; text: string }\n | { kind: 'failed'; text: string };\n\ninterface SessionRequest {\n mode: LiveDesktopMode;\n start: boolean;\n}\n\nconst Placeholder: FC<{ icon?: ReactNode; children: ReactNode; action?: ReactNode }> = ({\n icon,\n children,\n action,\n}) => (\n <div className=\"flex h-full w-full flex-col items-center justify-center gap-2 p-4 text-center\">\n {icon}\n <p className=\"max-w-72 text-muted-foreground text-xs leading-snug\">{children}</p>\n {action}\n </div>\n);\n\nexport interface ComputerLiveDesktopProps {\n assetId: string;\n className?: string;\n /** How long a viewer may take to show video ({@link LIVE_DESKTOP_READY_TIMEOUT_MS}). */\n readyTimeoutMs?: number;\n}\n\n/**\n * The computer's screen, live — watch it, or take control and use it — framed\n * from agora's Selkies viewer (agora `web/api/computer/live_desktop_viewer.py`).\n *\n * Opens watching as soon as it mounts on a running computer, and ends when it\n * unmounts, its workbench collapses to the rail, or the computer stops (an open\n * view keeps the computer awake, up to agora's 30-minute cap). Needs EDIT on\n * the computer, in its own workspace, and an app the computer serves on Athena\n * (agora binds the viewer to this page's origin). A computer without a display\n * (one not made from the Browser template) says so instead.\n *\n * The frame runs Selkies' page from the guest, sandboxed as Olympus frames it:\n * scripts on agora's origin, no popups, forms or top navigation. Its messages\n * only move this pane between connecting, connected and ended. Each computer\n * is its own instance (keyed by `assetId`), so a switch never shows or drives\n * the previous computer's screen.\n */\nexport const ComputerLiveDesktop: FC<ComputerLiveDesktopProps> = (props) => (\n <ComputerLiveDesktopView key={props.assetId} {...props} />\n);\n\nconst ComputerLiveDesktopView: FC<ComputerLiveDesktopProps> = ({\n assetId,\n className,\n readyTimeoutMs = LIVE_DESKTOP_READY_TIMEOUT_MS,\n}) => {\n const config = useAthenaConfig();\n const { backendUrl, apiKey, getToken } = config;\n const queryClient = useAthenaQueryClient();\n const status = useAthenaComputerStatus({ assetId });\n const state = status.data?.state.toLowerCase() ?? null;\n const running =\n state !== null && RUNNING_STATES.has(state) && status.data?.explicitStop !== 'stopping';\n // Hidden in a collapsed workbench, the pane stops its stream.\n const visible = useContext(SessionWorkbenchContext)?.isVisible ?? true;\n\n const [viewer, setViewer] = useState<MountedViewer | null>(null);\n const [notice, setNotice] = useState<Notice | null>(null);\n // A mode switch that failed while a viewer stayed on screen.\n const [switchError, setSwitchError] = useState<string | null>(null);\n const frameRef = useRef<HTMLIFrameElement>(null);\n const viewerRef = useRef<MountedViewer | null>(null);\n viewerRef.current = viewer;\n\n const session = useMutation<LiveDesktopSession, Error, SessionRequest>(\n {\n mutationFn: ({ mode, start }) =>\n createComputerLiveDesktopSession({\n backendUrl,\n // `config.token` is read at request time: the provider serves the\n // latest token through a getter.\n auth: { apiKey, token: config.token, getToken },\n assetId,\n mode,\n start,\n embedOrigin: typeof window === 'undefined' ? null : window.location.origin,\n }),\n onSuccess: (answer) => {\n setSwitchError(null);\n if (answer.status !== 'ready') {\n setViewer(null);\n setNotice({ kind: answer.status, text: answer.detail });\n return;\n }\n let url: string;\n try {\n url = liveDesktopViewerUrl({ backendUrl, viewerPath: answer.viewerPath });\n } catch {\n setViewer(null);\n setNotice({\n kind: 'failed',\n text: 'The computer’s desktop answered with a page this app will not open.',\n });\n return;\n }\n // The new page replaces the one on screen only now that it exists.\n setViewer({ url, mode: answer.mode, display: answer.display, state: 'connecting' });\n setNotice(null);\n },\n onError: (error) => {\n // A failed switch leaves the working viewer where it is.\n if (viewerRef.current) {\n setSwitchError(error.message);\n return;\n }\n setNotice({ kind: 'failed', text: error.message });\n },\n },\n queryClient,\n );\n const { mutate, reset } = session;\n\n const opened = useRef(false);\n const disconnectedByPerson = useRef(false);\n\n // Hidden, or the computer stopped: drop the view. Its ticket is spent, so\n // showing again (or the computer starting again) asks for a fresh one.\n useEffect(() => {\n if (running && visible) return;\n reset();\n setViewer(null);\n setSwitchError(null);\n opened.current = false;\n }, [running, visible, reset]);\n // Watch once the computer is known to be running; never wake one.\n useEffect(() => {\n if (!running || !visible || opened.current || disconnectedByPerson.current) return;\n opened.current = true;\n mutate({ mode: LiveDesktopMode.WATCH, start: false });\n }, [running, visible, mutate]);\n\n // The viewer page's own messages move it on; a page agora ended takes the\n // frame down (an unseen stream would keep the computer awake).\n const viewerUrl = viewer?.url ?? null;\n const viewerState = viewer?.state ?? null;\n useEffect(() => {\n if (!viewerUrl) return undefined;\n const origin = new URL(viewerUrl).origin;\n const end = (text: string) => {\n setViewer((current) => (current?.url === viewerUrl ? null : current));\n setNotice({ kind: 'ended', text });\n };\n const onMessage = (event: MessageEvent) => {\n if (event.origin !== origin || event.source !== frameRef.current?.contentWindow) return;\n const message = readLiveDesktopViewerMessage(event.data);\n if (!message) return;\n if (message.status === 'ready') {\n setViewer((current) =>\n current?.url === viewerUrl ? { ...current, state: 'connected' } : current,\n );\n } else if (message.status === 'error') {\n end(message.detail);\n } else if (message.status === 'closed') {\n const final = finalLiveDesktopCloseNotice(message.code);\n if (final) end(final);\n }\n };\n window.addEventListener('message', onMessage);\n return () => window.removeEventListener('message', onMessage);\n }, [viewerUrl]);\n // No video within the timeout takes the frame down too.\n useEffect(() => {\n if (!viewerUrl || viewerState !== 'connecting') return undefined;\n const timer = setTimeout(() => {\n setViewer((current) => (current?.url === viewerUrl ? null : current));\n setNotice({\n kind: 'ended',\n text: 'The desktop did not start streaming here. Reconnect to try again.',\n });\n }, readyTimeoutMs);\n return () => clearTimeout(timer);\n }, [viewerUrl, viewerState, readyTimeoutMs]);\n\n const watch = (start = false) => {\n disconnectedByPerson.current = false;\n opened.current = true;\n mutate({ mode: LiveDesktopMode.WATCH, start });\n };\n const control = () => mutate({ mode: LiveDesktopMode.CONTROL, start: false });\n const disconnect = () => {\n disconnectedByPerson.current = true;\n reset();\n setViewer(null);\n setNotice(null);\n setSwitchError(null);\n };\n\n const aspect = viewer?.display ? `${viewer.display.width} / ${viewer.display.height}` : '16 / 10';\n let body: ReactNode;\n if (!running) {\n body = (\n <Placeholder icon={<MonitorOffIcon className=\"size-5 text-muted-foreground/70\" aria-hidden />}>\n {status.isPending\n ? 'Checking the computer…'\n : 'The computer is not running. Its desktop shows here once it is.'}\n </Placeholder>\n );\n } else if (viewer) {\n body = (\n <>\n <iframe\n ref={frameRef}\n key={viewer.url}\n src={viewer.url}\n title=\"Live desktop\"\n className=\"h-full w-full border-0 bg-black\"\n sandbox=\"allow-scripts allow-same-origin allow-pointer-lock\"\n allow=\"fullscreen; keyboard-map; autoplay; clipboard-write\"\n referrerPolicy=\"no-referrer\"\n data-live-desktop-frame=\"\"\n />\n {viewer.state === 'connecting' || session.isPending ? (\n <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center bg-black/40\">\n <LoaderIcon className=\"size-5 animate-spin text-white\" aria-label=\"Connecting\" />\n </div>\n ) : null}\n {switchError ? (\n <div\n role=\"alert\"\n className=\"absolute inset-x-2 bottom-2 flex items-start gap-2 rounded-md border border-red-200 bg-red-50 px-2 py-1.5 text-red-900 text-xs\"\n >\n <AlertCircleIcon className=\"mt-0.5 size-3.5 shrink-0\" aria-hidden />\n <span className=\"min-w-0 flex-1\">{switchError}</span>\n <button\n type=\"button\"\n onClick={() => setSwitchError(null)}\n aria-label=\"Dismiss\"\n className=\"shrink-0 rounded p-0.5 hover:bg-red-100\"\n >\n <XIcon className=\"size-3\" aria-hidden />\n </button>\n </div>\n ) : null}\n </>\n );\n } else if (session.isPending) {\n body = (\n <Placeholder icon={<LoaderIcon className=\"size-5 animate-spin text-muted-foreground\" aria-hidden />}>\n Connecting to the desktop…\n </Placeholder>\n );\n } else if (notice?.kind === 'display_not_running') {\n body = (\n <Placeholder\n icon={<MonitorOffIcon className=\"size-5 text-muted-foreground/70\" aria-hidden />}\n action={\n <button type=\"button\" onClick={() => watch(true)} className={BUTTON_CLASS}>\n <PlayIcon className=\"size-3.5\" aria-hidden />\n Start the display\n </button>\n }\n >\n {notice.text}\n </Placeholder>\n );\n } else if (notice?.kind === 'unsupported') {\n body = (\n <Placeholder icon={<MonitorOffIcon className=\"size-5 text-muted-foreground/70\" aria-hidden />}>\n {notice.text}\n </Placeholder>\n );\n } else if (notice) {\n body = (\n <Placeholder\n icon={\n notice.kind === 'failed' ? (\n <AlertCircleIcon className=\"size-5 text-red-600\" aria-hidden />\n ) : (\n <MonitorOffIcon className=\"size-5 text-muted-foreground/70\" aria-hidden />\n )\n }\n action={\n <button type=\"button\" onClick={() => watch()} className={BUTTON_CLASS}>\n <RefreshCwIcon className=\"size-3.5\" aria-hidden />\n {notice.kind === 'failed' ? 'Try again' : 'Reconnect'}\n </button>\n }\n >\n {notice.text}\n </Placeholder>\n );\n } else {\n body = (\n <Placeholder\n icon={<EyeIcon className=\"size-5 text-muted-foreground/70\" aria-hidden />}\n action={\n <button type=\"button\" onClick={() => watch()} className={BUTTON_CLASS}>\n <EyeIcon className=\"size-3.5\" aria-hidden />\n Watch live\n </button>\n }\n >\n Watch the computer’s screen as the agent works.\n </Placeholder>\n );\n }\n\n const liveMode = running && viewer ? viewer.mode : null;\n return (\n <section aria-label=\"Live desktop\" className={cn('flex flex-col gap-1.5', className)}>\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-medium text-[11px] text-muted-foreground uppercase tracking-wide\">\n Live desktop\n </h3>\n {liveMode ? (\n <span\n data-mode={liveMode}\n className={cn(\n 'rounded-full px-1.5 py-px text-[10px]',\n liveMode === LiveDesktopMode.CONTROL\n ? 'bg-amber-100 text-amber-900'\n : 'bg-muted text-muted-foreground',\n )}\n >\n {liveMode === LiveDesktopMode.CONTROL ? 'In control' : 'Watching'}\n </span>\n ) : null}\n <div className=\"min-w-0 flex-1\" />\n {liveMode === LiveDesktopMode.WATCH ? (\n <button type=\"button\" onClick={control} disabled={session.isPending} className={BUTTON_CLASS}>\n <MousePointer2Icon className=\"size-3.5\" aria-hidden />\n Take control\n </button>\n ) : null}\n {liveMode === LiveDesktopMode.CONTROL ? (\n <button type=\"button\" onClick={() => watch()} disabled={session.isPending} className={BUTTON_CLASS}>\n <EyeIcon className=\"size-3.5\" aria-hidden />\n Stop controlling\n </button>\n ) : null}\n {liveMode ? (\n <button\n type=\"button\"\n onClick={disconnect}\n aria-label=\"Disconnect from the desktop\"\n title=\"Disconnect\"\n className={cn(BUTTON_CLASS, 'px-1.5')}\n >\n <XIcon className=\"size-3.5\" aria-hidden />\n </button>\n ) : null}\n </div>\n {/* The remote screen's aspect is a runtime value no static utility expresses. */}\n <div\n className=\"relative w-full overflow-hidden rounded-lg border border-border bg-muted/40\"\n style={{ aspectRatio: aspect }}\n >\n {body}\n </div>\n </section>\n );\n};\n"],"names":["_a"],"mappings":"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"}
|
|
@@ -60,8 +60,8 @@ const SessionWorkbenchInner = ({
|
|
|
60
60
|
);
|
|
61
61
|
const isPersisted = threadList ? !threadList.isNewChat : sessionPersisted === true || hasMessages;
|
|
62
62
|
const paneContext = react.useMemo(
|
|
63
|
-
() => ({ threadId, isPersisted, isRunning }),
|
|
64
|
-
[threadId, isPersisted, isRunning]
|
|
63
|
+
() => ({ threadId, isPersisted, isRunning, isVisible: !collapsed }),
|
|
64
|
+
[threadId, isPersisted, isRunning, collapsed]
|
|
65
65
|
);
|
|
66
66
|
const queryClient = reactQuery.useQueryClient();
|
|
67
67
|
const wasRunning = react.useRef(isRunning);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionWorkbench.cjs","sources":["../../src/workbench/SessionWorkbench.tsx"],"sourcesContent":["\"use client\";\n\nimport { useAuiState } from '@assistant-ui/react';\nimport { readDeepAgentStreamValues } from '@athenaintel/agent-runtime';\nimport { QueryClientProvider, useQueryClient } from '@tanstack/react-query';\nimport {\n LayersIcon,\n Maximize2Icon,\n Minimize2Icon,\n PanelRightCloseIcon,\n PanelRightOpenIcon,\n PlusIcon,\n SearchIcon,\n XIcon,\n} from 'lucide-react';\nimport {\n type FC,\n type KeyboardEvent,\n useContext,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useReducer,\n useRef,\n useState,\n} from 'react';\nimport { useAssetPanelStore } from '../assets/asset-panel-store';\nimport { AssetPanel } from '../assets/AssetPanel';\nimport { useAthenaQueryClient } from '../lib/use-athena-rest-query';\nimport { cn } from '../lib/utils';\nimport { useAthenaStatewireThreadList } from '../threads/statewire-thread-list';\nimport { AthenaStatewireThreadIdContext } from '../threads/thread-id-context';\nimport { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';\nimport {\n type SessionWorkbenchPane,\n type SessionWorkbenchPaneContext,\n SessionWorkbenchContext,\n} from './session-workbench-context';\nimport {\n filterWorkbenchPanes,\n threadTabs,\n visibleThreadTabs,\n type WorkbenchThreadTabs,\n workbenchTabsReducer,\n} from './session-workbench-model';\nimport {\n DEFAULT_SESSION_WORKBENCH_OPEN_PANE_IDS,\n DEFAULT_SESSION_WORKBENCH_PANES,\n} from './session-workbench-panes';\nimport { SESSION_WORKSPACE_QUERY_KEY } from './use-session-workspace';\nimport { useWorkbenchLayout } from './workbench-layout-context';\n\n/** The tab `AthenaLayout`'s workbench shows the open assets in. */\nexport const SESSION_WORKBENCH_ASSETS_PANE_ID = 'open-assets';\n\nconst OPEN_ASSETS_PANE: SessionWorkbenchPane = {\n id: SESSION_WORKBENCH_ASSETS_PANE_ID,\n title: 'Open assets',\n description: 'The documents, sheets and decks open in this session',\n icon: LayersIcon,\n render: () => <AssetPanel manageAssetPanelHostRegistration={false} />,\n};\n\nexport interface SessionWorkbenchProps {\n /**\n * The panes the \"+\" menu offers, in order. Defaults to the built-in Tasks,\n * Shell, Computer, Pull requests and Attachments\n * (`DEFAULT_SESSION_WORKBENCH_PANES`); spread it to add your own.\n */\n panes?: readonly SessionWorkbenchPane[];\n /**\n * The panes a thread opens with until the person changes its tabs (ids from\n * `panes`; unknown ids are ignored). Defaults to Tasks, Shell and Computer.\n */\n defaultOpenPaneIds?: readonly string[];\n className?: string;\n}\n\nconst ICON_BUTTON_CLASS =\n 'inline-flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring/60';\n\n/**\n * A Devin-style workspace pane for the active session: a tab strip of panes\n * (Tasks, Shell, Computer, Pull requests, Attachments — and any the host\n * adds), a \"+\" menu to open more, closable tabs, and a collapse to a rail.\n *\n * Mount it inside `<AthenaProvider>` — as `AthenaLayout`'s `workbench` for a\n * resizable right-hand column, or anywhere in your own layout. Each thread\n * keeps its own open tabs, and whether the workspace is open or collapsed,\n * while the workbench stays mounted (in memory only); a thread it has not seen\n * opens `defaultOpenPaneIds`, collapsed when the layout's\n * `defaultWorkbenchCollapsed` says so. In `AthenaLayout`, the assets the agent\n * or the person opens show here too, as an \"Open assets\" tab.\n */\nexport const SessionWorkbench: FC<SessionWorkbenchProps> = (props) => {\n // One client for every pane, so the workspace read several panes share is\n // fetched once even when the host provides no QueryClient.\n const queryClient = useAthenaQueryClient();\n return (\n <QueryClientProvider client={queryClient}>\n <SessionWorkbenchInner {...props} />\n </QueryClientProvider>\n );\n};\n\nconst SessionWorkbenchInner: FC<SessionWorkbenchProps> = ({\n panes = DEFAULT_SESSION_WORKBENCH_PANES,\n defaultOpenPaneIds = DEFAULT_SESSION_WORKBENCH_OPEN_PANE_IDS,\n className,\n}) => {\n const layout = useWorkbenchLayout();\n const [ownCollapsed, setOwnCollapsed] = useState(false);\n const collapsed = layout ? layout.collapsed : ownCollapsed;\n const setCollapsed = layout ? layout.setCollapsed : setOwnCollapsed;\n // Only a hosting layout can give the workbench the chat's width.\n const maximized = layout?.maximized ?? false;\n const setMaximized = layout?.setMaximized;\n const hostsAssetPanel = layout?.hostsAssetPanel ?? false;\n\n const threadId = useContext(AthenaStatewireThreadIdContext);\n const threadList = useAthenaStatewireThreadList();\n const hasMessages = useAuiState((state) => state.thread.messages.length > 0);\n // The attach snapshot says whether the backend minted the thread's session\n // (`sessionPersisted`) — true for an existing thread before any message.\n const sessionPersisted = useAuiState(\n (state) =>\n readDeepAgentStreamValues((state.thread.extras as { state?: unknown } | undefined)?.state)\n .sessionPersisted,\n );\n const isRunning = useAuiState(\n (state) => (state.thread.extras as { status?: unknown } | undefined)?.status === 'running',\n );\n // In thread-list mode the list knows whether the backend has the thread; a\n // single-thread provider's thread is known once the host says its session\n // exists, or once it has history (a host that predates the flag).\n const isPersisted = threadList\n ? !threadList.isNewChat\n : sessionPersisted === true || hasMessages;\n const paneContext = useMemo<SessionWorkbenchPaneContext>(\n () => ({ threadId, isPersisted, isRunning }),\n [threadId, isPersisted, isRunning],\n );\n\n // Polling stops with the run; read the workspace once more as it settles so\n // what the run's last steps created shows without waiting for a refocus.\n const queryClient = useQueryClient();\n const wasRunning = useRef(isRunning);\n useEffect(() => {\n if (wasRunning.current && !isRunning) {\n void queryClient.invalidateQueries({ queryKey: SESSION_WORKSPACE_QUERY_KEY });\n }\n wasRunning.current = isRunning;\n }, [isRunning, queryClient]);\n\n const assetPanelOpen = useAssetPanelStore((state) => state.isOpen);\n const closeAssetPanel = useAssetPanelStore((state) => state.closePanel);\n const allPanes = useMemo<readonly SessionWorkbenchPane[]>(\n () => (hostsAssetPanel && assetPanelOpen ? [...panes, OPEN_ASSETS_PANE] : panes),\n [assetPanelOpen, hostsAssetPanel, panes],\n );\n const paneById = useMemo(() => new Map(allPanes.map((pane) => [pane.id, pane])), [allPanes]);\n const paneIds = useMemo(() => new Set(paneById.keys()), [paneById]);\n\n const defaults = useMemo<WorkbenchThreadTabs>(() => {\n const open = defaultOpenPaneIds.filter((id) => panes.some((pane) => pane.id === id));\n return { open, active: open[0] ?? null };\n }, [defaultOpenPaneIds, panes]);\n const [tabsState, dispatchTabs] = useReducer(workbenchTabsReducer, {});\n const threadKey = threadId ?? '';\n\n // Whether the workspace is open is kept per thread, like its tabs: a thread\n // the person has not opened or collapsed it in shows the layout's default (a\n // new session, or one switched to), so one session's open workspace never\n // follows the person into the next. Applied before paint, so a switch never\n // flashes the previous thread's state.\n const defaultCollapsed = layout?.defaultCollapsed ?? false;\n const collapsedByThread = useRef(new Map<string, boolean>());\n const setThreadCollapsed = (next: boolean) => {\n collapsedByThread.current.set(threadKey, next);\n setCollapsed(next);\n };\n useLayoutEffect(() => {\n setCollapsed(collapsedByThread.current.get(threadKey) ?? defaultCollapsed);\n }, [threadKey, defaultCollapsed, setCollapsed]);\n let tabs = visibleThreadTabs(threadTabs(tabsState, threadKey, defaults), paneIds);\n // Assets already open when the workbench mounts (or when a thread with open\n // assets comes back) show their tab without an opening to react to.\n if (\n paneIds.has(SESSION_WORKBENCH_ASSETS_PANE_ID) &&\n !tabs.open.includes(SESSION_WORKBENCH_ASSETS_PANE_ID)\n ) {\n tabs = {\n open: [...tabs.open, SESSION_WORKBENCH_ASSETS_PANE_ID],\n active: tabs.active ?? SESSION_WORKBENCH_ASSETS_PANE_ID,\n };\n }\n\n // An asset opening (a tool card, a citation, the Attachments pane) brings\n // the \"Open assets\" tab forward and the workbench out of its rail. A thread\n // switch restoring that thread's open assets (`setCurrentThread`) is not an\n // opening: the thread keeps the pane it had selected. Read through refs:\n // the store notifies outside React's render.\n const latest = useRef({ threadKey, defaults, setCollapsed });\n latest.current = { threadKey, defaults, setCollapsed };\n useEffect(() => {\n if (!hostsAssetPanel) return undefined;\n return useAssetPanelStore.subscribe((state, previous) => {\n if (state.currentThreadId !== previous.currentThreadId) return;\n // Every open request, a repeat click on the asset already showing too.\n if (!state.isOpen || state.openRequestSeq === previous.openRequestSeq) return;\n dispatchTabs({\n type: 'open',\n threadKey: latest.current.threadKey,\n paneId: SESSION_WORKBENCH_ASSETS_PANE_ID,\n defaults: latest.current.defaults,\n });\n collapsedByThread.current.set(latest.current.threadKey, false);\n latest.current.setCollapsed(false);\n });\n }, [hostsAssetPanel]);\n\n // Showing a tab the strip renders is an `open`: it activates a stored tab\n // and records one the strip only derived (the \"Open assets\" tab above).\n const openPane = (paneId: string) =>\n dispatchTabs({ type: 'open', threadKey, paneId, defaults });\n const activatePane = openPane;\n const closePane = (paneId: string) => {\n if (paneId === SESSION_WORKBENCH_ASSETS_PANE_ID) closeAssetPanel();\n dispatchTabs({ type: 'close', threadKey, paneId, defaults });\n };\n\n const baseId = useId();\n const tabId = (paneId: string) => `${baseId}-tab-${paneId}`;\n const panelId = `${baseId}-panel`;\n\n const onTabKeyDown = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {\n const step = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0;\n if (step === 0 || tabs.open.length === 0) return;\n event.preventDefault();\n const nextId = tabs.open[(index + step + tabs.open.length) % tabs.open.length];\n if (!nextId) return;\n activatePane(nextId);\n event.currentTarget.ownerDocument.getElementById(tabId(nextId))?.focus();\n };\n\n const activePane = tabs.active ? paneById.get(tabs.active) : undefined;\n const assetsPane = paneById.get(SESSION_WORKBENCH_ASSETS_PANE_ID);\n const assetsActive = activePane?.id === SESSION_WORKBENCH_ASSETS_PANE_ID;\n\n // One tree for both states: the panel stays mounted (hidden) while the\n // workbench is a rail, and the open assets stay mounted (hidden) while\n // another pane shows — their iframes keep their editing session, as the\n // asset panel's own tabs do.\n return (\n <SessionWorkbenchContext.Provider value={paneContext}>\n <aside\n aria-label=\"Session workspace\"\n className={cn(\n 'athena-sdk-chrome flex h-full flex-col border-border border-l bg-background',\n collapsed ? 'w-10 shrink-0 items-center gap-1 py-2' : 'min-h-0 w-full min-w-0',\n className,\n )}\n >\n {collapsed ? (\n <>\n <button\n type=\"button\"\n aria-label=\"Expand workspace\"\n title=\"Expand workspace\"\n onClick={() => setThreadCollapsed(false)}\n className={ICON_BUTTON_CLASS}\n >\n <PanelRightOpenIcon className=\"size-4\" />\n </button>\n {tabs.open.map((paneId) => {\n const pane = paneById.get(paneId);\n if (!pane) return null;\n const Icon = pane.icon ?? LayersIcon;\n return (\n <button\n key={paneId}\n type=\"button\"\n aria-label={`Show ${pane.title}`}\n title={pane.title}\n onClick={() => {\n activatePane(paneId);\n setThreadCollapsed(false);\n }}\n className={cn(\n ICON_BUTTON_CLASS,\n paneId === tabs.active && 'bg-muted text-foreground',\n )}\n >\n <Icon className=\"size-4\" />\n </button>\n );\n })}\n </>\n ) : (\n <div className=\"flex h-11 w-full shrink-0 items-center gap-1 border-border border-b pr-1.5 pl-2\">\n <div\n role=\"tablist\"\n aria-label=\"Workspace panes\"\n className=\"flex min-w-0 items-center gap-1 overflow-x-auto [scrollbar-width:none]\"\n >\n {tabs.open.map((paneId, index) => {\n const pane = paneById.get(paneId);\n if (!pane) return null;\n const Icon = pane.icon;\n const selected = paneId === tabs.active;\n return (\n <div\n key={paneId}\n data-selected={selected ? '' : undefined}\n className={cn(\n 'group/tab flex h-7 shrink-0 items-center rounded-md text-[13px] transition-colors',\n selected\n ? 'bg-foreground/[0.06] text-foreground'\n : 'text-muted-foreground hover:bg-foreground/[0.04] hover:text-foreground',\n )}\n >\n <button\n type=\"button\"\n role=\"tab\"\n id={tabId(paneId)}\n aria-selected={selected}\n aria-controls={panelId}\n tabIndex={selected ? 0 : -1}\n onClick={() => activatePane(paneId)}\n onKeyDown={(event) => onTabKeyDown(event, index)}\n className=\"flex h-full items-center gap-1.5 rounded-md pr-1 pl-2 outline-none focus-visible:ring-2 focus-visible:ring-ring/60\"\n >\n {Icon ? <Icon className=\"size-3.5\" /> : null}\n {pane.title}\n </button>\n {/* Quiet until the tab is hovered, focused or selected, as in Devin's strip. */}\n <button\n type=\"button\"\n aria-label={`Close ${pane.title}`}\n title={`Close ${pane.title}`}\n onClick={() => closePane(paneId)}\n className={cn(\n 'mr-1 rounded p-0.5 transition-opacity hover:bg-background hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/60',\n selected ? 'opacity-60' : 'opacity-0 group-hover/tab:opacity-60',\n )}\n >\n <XIcon className=\"size-3\" />\n </button>\n </div>\n );\n })}\n </div>\n <AddPaneMenu panes={allPanes} openIds={tabs.open} onOpen={openPane} />\n <div className=\"min-w-0 flex-1\" />\n {setMaximized ? (\n <button\n type=\"button\"\n aria-label={maximized ? 'Restore workspace' : 'Maximize workspace'}\n title={maximized ? 'Restore workspace' : 'Maximize workspace'}\n aria-pressed={maximized}\n onClick={() => setMaximized(!maximized)}\n className={ICON_BUTTON_CLASS}\n >\n {maximized ? <Minimize2Icon className=\"size-4\" /> : <Maximize2Icon className=\"size-4\" />}\n </button>\n ) : null}\n <button\n type=\"button\"\n aria-label=\"Collapse workspace\"\n title=\"Collapse workspace\"\n onClick={() => setThreadCollapsed(true)}\n className={ICON_BUTTON_CLASS}\n >\n <PanelRightCloseIcon className=\"size-4\" />\n </button>\n </div>\n )}\n <div\n role=\"tabpanel\"\n id={panelId}\n aria-labelledby={activePane ? tabId(activePane.id) : undefined}\n className={cn(\n 'min-h-0 w-full flex-1',\n assetsActive ? 'flex flex-col' : 'overflow-y-auto',\n collapsed && 'hidden',\n )}\n >\n {assetsActive ? null : activePane ? (\n activePane.render(paneContext)\n ) : (\n <NoOpenPane panes={allPanes} onOpen={openPane} />\n )}\n {assetsPane ? (\n <div\n key={SESSION_WORKBENCH_ASSETS_PANE_ID}\n data-workbench-assets=\"\"\n className={cn(assetsActive ? 'flex min-h-0 flex-1 flex-col' : 'hidden')}\n >\n {assetsPane.render(paneContext)}\n </div>\n ) : null}\n </div>\n </aside>\n </SessionWorkbenchContext.Provider>\n );\n};\n\nconst PaneMenuItems: FC<{\n panes: readonly SessionWorkbenchPane[];\n openIds: readonly string[];\n onOpen: (paneId: string) => void;\n}> = ({ panes, openIds, onOpen }) => (\n <ul className=\"flex flex-col\">\n {panes.map((pane) => {\n const Icon = pane.icon ?? LayersIcon;\n const isOpen = openIds.includes(pane.id);\n return (\n <li key={pane.id}>\n <button\n type=\"button\"\n title={pane.description}\n onClick={() => onOpen(pane.id)}\n className=\"flex h-[30px] w-full items-center gap-2 rounded-md px-2 text-left text-[13px] transition-colors hover:bg-muted focus-visible:bg-muted focus-visible:outline-none\"\n >\n <Icon className=\"size-4 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate\">{pane.title}</span>\n {isOpen ? <span className=\"text-[11px] text-muted-foreground\">Open</span> : null}\n </button>\n </li>\n );\n })}\n </ul>\n);\n\nconst AddPaneMenu: FC<{\n panes: readonly SessionWorkbenchPane[];\n openIds: readonly string[];\n onOpen: (paneId: string) => void;\n}> = ({ panes, openIds, onOpen }) => {\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState('');\n const shown = filterWorkbenchPanes(panes, query);\n const choose = (paneId: string) => {\n onOpen(paneId);\n setOpen(false);\n setQuery('');\n };\n return (\n <Popover\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) setQuery('');\n }}\n >\n <PopoverTrigger asChild>\n <button type=\"button\" aria-label=\"Open a pane\" title=\"Open a pane\" className={ICON_BUTTON_CLASS}>\n <PlusIcon className=\"size-4\" />\n </button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-56 p-1\" aria-label=\"Panes\">\n <div className=\"flex items-center gap-2 border-border border-b px-2 pb-1.5 pt-1\">\n <SearchIcon className=\"size-3.5 shrink-0 text-muted-foreground\" aria-hidden />\n <input\n // biome-ignore lint/a11y/noAutofocus: the menu opens to type into, as a command menu does.\n autoFocus\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onKeyDown={(event) => {\n const first = shown[0];\n if (event.key === 'Enter' && first) {\n event.preventDefault();\n choose(first.id);\n }\n }}\n placeholder=\"Search\"\n aria-label=\"Search panes\"\n className=\"h-7 min-w-0 flex-1 bg-transparent text-[13px] outline-none placeholder:text-muted-foreground\"\n />\n </div>\n <div className=\"pt-1\">\n {shown.length > 0 ? (\n <PaneMenuItems panes={shown} openIds={openIds} onOpen={choose} />\n ) : (\n <p className=\"px-2 py-1.5 text-[13px] text-muted-foreground\">No pane matches.</p>\n )}\n </div>\n </PopoverContent>\n </Popover>\n );\n};\n\n/** Nothing open: every pane the \"+\" menu offers, with what each shows, centred in the column. */\nconst NoOpenPane: FC<{\n panes: readonly SessionWorkbenchPane[];\n onOpen: (paneId: string) => void;\n}> = ({ panes, onOpen }) => (\n <div className=\"flex min-h-full items-center justify-center p-6\">\n <ul aria-label=\"Available panes\" className=\"flex w-full max-w-xs flex-col gap-1\">\n {panes.map((pane) => {\n const Icon = pane.icon ?? LayersIcon;\n return (\n <li key={pane.id}>\n <button\n type=\"button\"\n onClick={() => onOpen(pane.id)}\n className=\"flex w-full items-start gap-3 rounded-lg px-3 py-2 text-left transition-colors hover:bg-muted focus-visible:bg-muted focus-visible:outline-none\"\n >\n <Icon className=\"mt-0.5 size-4 shrink-0 text-muted-foreground\" />\n <span className=\"flex min-w-0 flex-col\">\n <span className=\"text-[13px] text-foreground\">{pane.title}</span>\n {pane.description ? (\n <span className=\"text-muted-foreground text-xs\">{pane.description}</span>\n ) : null}\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n);\n"],"names":["LayersIcon","jsx","AssetPanel","useAthenaQueryClient","QueryClientProvider","DEFAULT_SESSION_WORKBENCH_PANES","DEFAULT_SESSION_WORKBENCH_OPEN_PANE_IDS","useWorkbenchLayout","useState","useContext","AthenaStatewireThreadIdContext","useAthenaStatewireThreadList","useAuiState","readDeepAgentStreamValues","useMemo","useQueryClient","useRef","useEffect","SESSION_WORKSPACE_QUERY_KEY","useAssetPanelStore","useReducer","workbenchTabsReducer","useLayoutEffect","visibleThreadTabs","threadTabs","useId","SessionWorkbenchContext","jsxs","cn","Fragment","PanelRightOpenIcon","XIcon","Minimize2Icon","Maximize2Icon","PanelRightCloseIcon","filterWorkbenchPanes","Popover","PopoverTrigger","PlusIcon","PopoverContent","SearchIcon"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SessionWorkbench.cjs","sources":["../../src/workbench/SessionWorkbench.tsx"],"sourcesContent":["\"use client\";\n\nimport { useAuiState } from '@assistant-ui/react';\nimport { readDeepAgentStreamValues } from '@athenaintel/agent-runtime';\nimport { QueryClientProvider, useQueryClient } from '@tanstack/react-query';\nimport {\n LayersIcon,\n Maximize2Icon,\n Minimize2Icon,\n PanelRightCloseIcon,\n PanelRightOpenIcon,\n PlusIcon,\n SearchIcon,\n XIcon,\n} from 'lucide-react';\nimport {\n type FC,\n type KeyboardEvent,\n useContext,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useReducer,\n useRef,\n useState,\n} from 'react';\nimport { useAssetPanelStore } from '../assets/asset-panel-store';\nimport { AssetPanel } from '../assets/AssetPanel';\nimport { useAthenaQueryClient } from '../lib/use-athena-rest-query';\nimport { cn } from '../lib/utils';\nimport { useAthenaStatewireThreadList } from '../threads/statewire-thread-list';\nimport { AthenaStatewireThreadIdContext } from '../threads/thread-id-context';\nimport { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';\nimport {\n type SessionWorkbenchPane,\n type SessionWorkbenchPaneContext,\n SessionWorkbenchContext,\n} from './session-workbench-context';\nimport {\n filterWorkbenchPanes,\n threadTabs,\n visibleThreadTabs,\n type WorkbenchThreadTabs,\n workbenchTabsReducer,\n} from './session-workbench-model';\nimport {\n DEFAULT_SESSION_WORKBENCH_OPEN_PANE_IDS,\n DEFAULT_SESSION_WORKBENCH_PANES,\n} from './session-workbench-panes';\nimport { SESSION_WORKSPACE_QUERY_KEY } from './use-session-workspace';\nimport { useWorkbenchLayout } from './workbench-layout-context';\n\n/** The tab `AthenaLayout`'s workbench shows the open assets in. */\nexport const SESSION_WORKBENCH_ASSETS_PANE_ID = 'open-assets';\n\nconst OPEN_ASSETS_PANE: SessionWorkbenchPane = {\n id: SESSION_WORKBENCH_ASSETS_PANE_ID,\n title: 'Open assets',\n description: 'The documents, sheets and decks open in this session',\n icon: LayersIcon,\n render: () => <AssetPanel manageAssetPanelHostRegistration={false} />,\n};\n\nexport interface SessionWorkbenchProps {\n /**\n * The panes the \"+\" menu offers, in order. Defaults to the built-in Tasks,\n * Shell, Computer, Pull requests and Attachments\n * (`DEFAULT_SESSION_WORKBENCH_PANES`); spread it to add your own.\n */\n panes?: readonly SessionWorkbenchPane[];\n /**\n * The panes a thread opens with until the person changes its tabs (ids from\n * `panes`; unknown ids are ignored). Defaults to Tasks, Shell and Computer.\n */\n defaultOpenPaneIds?: readonly string[];\n className?: string;\n}\n\nconst ICON_BUTTON_CLASS =\n 'inline-flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring/60';\n\n/**\n * A Devin-style workspace pane for the active session: a tab strip of panes\n * (Tasks, Shell, Computer, Pull requests, Attachments — and any the host\n * adds), a \"+\" menu to open more, closable tabs, and a collapse to a rail.\n *\n * Mount it inside `<AthenaProvider>` — as `AthenaLayout`'s `workbench` for a\n * resizable right-hand column, or anywhere in your own layout. Each thread\n * keeps its own open tabs, and whether the workspace is open or collapsed,\n * while the workbench stays mounted (in memory only); a thread it has not seen\n * opens `defaultOpenPaneIds`, collapsed when the layout's\n * `defaultWorkbenchCollapsed` says so. In `AthenaLayout`, the assets the agent\n * or the person opens show here too, as an \"Open assets\" tab.\n */\nexport const SessionWorkbench: FC<SessionWorkbenchProps> = (props) => {\n // One client for every pane, so the workspace read several panes share is\n // fetched once even when the host provides no QueryClient.\n const queryClient = useAthenaQueryClient();\n return (\n <QueryClientProvider client={queryClient}>\n <SessionWorkbenchInner {...props} />\n </QueryClientProvider>\n );\n};\n\nconst SessionWorkbenchInner: FC<SessionWorkbenchProps> = ({\n panes = DEFAULT_SESSION_WORKBENCH_PANES,\n defaultOpenPaneIds = DEFAULT_SESSION_WORKBENCH_OPEN_PANE_IDS,\n className,\n}) => {\n const layout = useWorkbenchLayout();\n const [ownCollapsed, setOwnCollapsed] = useState(false);\n const collapsed = layout ? layout.collapsed : ownCollapsed;\n const setCollapsed = layout ? layout.setCollapsed : setOwnCollapsed;\n // Only a hosting layout can give the workbench the chat's width.\n const maximized = layout?.maximized ?? false;\n const setMaximized = layout?.setMaximized;\n const hostsAssetPanel = layout?.hostsAssetPanel ?? false;\n\n const threadId = useContext(AthenaStatewireThreadIdContext);\n const threadList = useAthenaStatewireThreadList();\n const hasMessages = useAuiState((state) => state.thread.messages.length > 0);\n // The attach snapshot says whether the backend minted the thread's session\n // (`sessionPersisted`) — true for an existing thread before any message.\n const sessionPersisted = useAuiState(\n (state) =>\n readDeepAgentStreamValues((state.thread.extras as { state?: unknown } | undefined)?.state)\n .sessionPersisted,\n );\n const isRunning = useAuiState(\n (state) => (state.thread.extras as { status?: unknown } | undefined)?.status === 'running',\n );\n // In thread-list mode the list knows whether the backend has the thread; a\n // single-thread provider's thread is known once the host says its session\n // exists, or once it has history (a host that predates the flag).\n const isPersisted = threadList\n ? !threadList.isNewChat\n : sessionPersisted === true || hasMessages;\n const paneContext = useMemo<SessionWorkbenchPaneContext>(\n () => ({ threadId, isPersisted, isRunning, isVisible: !collapsed }),\n [threadId, isPersisted, isRunning, collapsed],\n );\n\n // Polling stops with the run; read the workspace once more as it settles so\n // what the run's last steps created shows without waiting for a refocus.\n const queryClient = useQueryClient();\n const wasRunning = useRef(isRunning);\n useEffect(() => {\n if (wasRunning.current && !isRunning) {\n void queryClient.invalidateQueries({ queryKey: SESSION_WORKSPACE_QUERY_KEY });\n }\n wasRunning.current = isRunning;\n }, [isRunning, queryClient]);\n\n const assetPanelOpen = useAssetPanelStore((state) => state.isOpen);\n const closeAssetPanel = useAssetPanelStore((state) => state.closePanel);\n const allPanes = useMemo<readonly SessionWorkbenchPane[]>(\n () => (hostsAssetPanel && assetPanelOpen ? [...panes, OPEN_ASSETS_PANE] : panes),\n [assetPanelOpen, hostsAssetPanel, panes],\n );\n const paneById = useMemo(() => new Map(allPanes.map((pane) => [pane.id, pane])), [allPanes]);\n const paneIds = useMemo(() => new Set(paneById.keys()), [paneById]);\n\n const defaults = useMemo<WorkbenchThreadTabs>(() => {\n const open = defaultOpenPaneIds.filter((id) => panes.some((pane) => pane.id === id));\n return { open, active: open[0] ?? null };\n }, [defaultOpenPaneIds, panes]);\n const [tabsState, dispatchTabs] = useReducer(workbenchTabsReducer, {});\n const threadKey = threadId ?? '';\n\n // Whether the workspace is open is kept per thread, like its tabs: a thread\n // the person has not opened or collapsed it in shows the layout's default (a\n // new session, or one switched to), so one session's open workspace never\n // follows the person into the next. Applied before paint, so a switch never\n // flashes the previous thread's state.\n const defaultCollapsed = layout?.defaultCollapsed ?? false;\n const collapsedByThread = useRef(new Map<string, boolean>());\n const setThreadCollapsed = (next: boolean) => {\n collapsedByThread.current.set(threadKey, next);\n setCollapsed(next);\n };\n useLayoutEffect(() => {\n setCollapsed(collapsedByThread.current.get(threadKey) ?? defaultCollapsed);\n }, [threadKey, defaultCollapsed, setCollapsed]);\n let tabs = visibleThreadTabs(threadTabs(tabsState, threadKey, defaults), paneIds);\n // Assets already open when the workbench mounts (or when a thread with open\n // assets comes back) show their tab without an opening to react to.\n if (\n paneIds.has(SESSION_WORKBENCH_ASSETS_PANE_ID) &&\n !tabs.open.includes(SESSION_WORKBENCH_ASSETS_PANE_ID)\n ) {\n tabs = {\n open: [...tabs.open, SESSION_WORKBENCH_ASSETS_PANE_ID],\n active: tabs.active ?? SESSION_WORKBENCH_ASSETS_PANE_ID,\n };\n }\n\n // An asset opening (a tool card, a citation, the Attachments pane) brings\n // the \"Open assets\" tab forward and the workbench out of its rail. A thread\n // switch restoring that thread's open assets (`setCurrentThread`) is not an\n // opening: the thread keeps the pane it had selected. Read through refs:\n // the store notifies outside React's render.\n const latest = useRef({ threadKey, defaults, setCollapsed });\n latest.current = { threadKey, defaults, setCollapsed };\n useEffect(() => {\n if (!hostsAssetPanel) return undefined;\n return useAssetPanelStore.subscribe((state, previous) => {\n if (state.currentThreadId !== previous.currentThreadId) return;\n // Every open request, a repeat click on the asset already showing too.\n if (!state.isOpen || state.openRequestSeq === previous.openRequestSeq) return;\n dispatchTabs({\n type: 'open',\n threadKey: latest.current.threadKey,\n paneId: SESSION_WORKBENCH_ASSETS_PANE_ID,\n defaults: latest.current.defaults,\n });\n collapsedByThread.current.set(latest.current.threadKey, false);\n latest.current.setCollapsed(false);\n });\n }, [hostsAssetPanel]);\n\n // Showing a tab the strip renders is an `open`: it activates a stored tab\n // and records one the strip only derived (the \"Open assets\" tab above).\n const openPane = (paneId: string) =>\n dispatchTabs({ type: 'open', threadKey, paneId, defaults });\n const activatePane = openPane;\n const closePane = (paneId: string) => {\n if (paneId === SESSION_WORKBENCH_ASSETS_PANE_ID) closeAssetPanel();\n dispatchTabs({ type: 'close', threadKey, paneId, defaults });\n };\n\n const baseId = useId();\n const tabId = (paneId: string) => `${baseId}-tab-${paneId}`;\n const panelId = `${baseId}-panel`;\n\n const onTabKeyDown = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {\n const step = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0;\n if (step === 0 || tabs.open.length === 0) return;\n event.preventDefault();\n const nextId = tabs.open[(index + step + tabs.open.length) % tabs.open.length];\n if (!nextId) return;\n activatePane(nextId);\n event.currentTarget.ownerDocument.getElementById(tabId(nextId))?.focus();\n };\n\n const activePane = tabs.active ? paneById.get(tabs.active) : undefined;\n const assetsPane = paneById.get(SESSION_WORKBENCH_ASSETS_PANE_ID);\n const assetsActive = activePane?.id === SESSION_WORKBENCH_ASSETS_PANE_ID;\n\n // One tree for both states: the panel stays mounted (hidden) while the\n // workbench is a rail, and the open assets stay mounted (hidden) while\n // another pane shows — their iframes keep their editing session, as the\n // asset panel's own tabs do.\n return (\n <SessionWorkbenchContext.Provider value={paneContext}>\n <aside\n aria-label=\"Session workspace\"\n className={cn(\n 'athena-sdk-chrome flex h-full flex-col border-border border-l bg-background',\n collapsed ? 'w-10 shrink-0 items-center gap-1 py-2' : 'min-h-0 w-full min-w-0',\n className,\n )}\n >\n {collapsed ? (\n <>\n <button\n type=\"button\"\n aria-label=\"Expand workspace\"\n title=\"Expand workspace\"\n onClick={() => setThreadCollapsed(false)}\n className={ICON_BUTTON_CLASS}\n >\n <PanelRightOpenIcon className=\"size-4\" />\n </button>\n {tabs.open.map((paneId) => {\n const pane = paneById.get(paneId);\n if (!pane) return null;\n const Icon = pane.icon ?? LayersIcon;\n return (\n <button\n key={paneId}\n type=\"button\"\n aria-label={`Show ${pane.title}`}\n title={pane.title}\n onClick={() => {\n activatePane(paneId);\n setThreadCollapsed(false);\n }}\n className={cn(\n ICON_BUTTON_CLASS,\n paneId === tabs.active && 'bg-muted text-foreground',\n )}\n >\n <Icon className=\"size-4\" />\n </button>\n );\n })}\n </>\n ) : (\n <div className=\"flex h-11 w-full shrink-0 items-center gap-1 border-border border-b pr-1.5 pl-2\">\n <div\n role=\"tablist\"\n aria-label=\"Workspace panes\"\n className=\"flex min-w-0 items-center gap-1 overflow-x-auto [scrollbar-width:none]\"\n >\n {tabs.open.map((paneId, index) => {\n const pane = paneById.get(paneId);\n if (!pane) return null;\n const Icon = pane.icon;\n const selected = paneId === tabs.active;\n return (\n <div\n key={paneId}\n data-selected={selected ? '' : undefined}\n className={cn(\n 'group/tab flex h-7 shrink-0 items-center rounded-md text-[13px] transition-colors',\n selected\n ? 'bg-foreground/[0.06] text-foreground'\n : 'text-muted-foreground hover:bg-foreground/[0.04] hover:text-foreground',\n )}\n >\n <button\n type=\"button\"\n role=\"tab\"\n id={tabId(paneId)}\n aria-selected={selected}\n aria-controls={panelId}\n tabIndex={selected ? 0 : -1}\n onClick={() => activatePane(paneId)}\n onKeyDown={(event) => onTabKeyDown(event, index)}\n className=\"flex h-full items-center gap-1.5 rounded-md pr-1 pl-2 outline-none focus-visible:ring-2 focus-visible:ring-ring/60\"\n >\n {Icon ? <Icon className=\"size-3.5\" /> : null}\n {pane.title}\n </button>\n {/* Quiet until the tab is hovered, focused or selected, as in Devin's strip. */}\n <button\n type=\"button\"\n aria-label={`Close ${pane.title}`}\n title={`Close ${pane.title}`}\n onClick={() => closePane(paneId)}\n className={cn(\n 'mr-1 rounded p-0.5 transition-opacity hover:bg-background hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/60',\n selected ? 'opacity-60' : 'opacity-0 group-hover/tab:opacity-60',\n )}\n >\n <XIcon className=\"size-3\" />\n </button>\n </div>\n );\n })}\n </div>\n <AddPaneMenu panes={allPanes} openIds={tabs.open} onOpen={openPane} />\n <div className=\"min-w-0 flex-1\" />\n {setMaximized ? (\n <button\n type=\"button\"\n aria-label={maximized ? 'Restore workspace' : 'Maximize workspace'}\n title={maximized ? 'Restore workspace' : 'Maximize workspace'}\n aria-pressed={maximized}\n onClick={() => setMaximized(!maximized)}\n className={ICON_BUTTON_CLASS}\n >\n {maximized ? <Minimize2Icon className=\"size-4\" /> : <Maximize2Icon className=\"size-4\" />}\n </button>\n ) : null}\n <button\n type=\"button\"\n aria-label=\"Collapse workspace\"\n title=\"Collapse workspace\"\n onClick={() => setThreadCollapsed(true)}\n className={ICON_BUTTON_CLASS}\n >\n <PanelRightCloseIcon className=\"size-4\" />\n </button>\n </div>\n )}\n <div\n role=\"tabpanel\"\n id={panelId}\n aria-labelledby={activePane ? tabId(activePane.id) : undefined}\n className={cn(\n 'min-h-0 w-full flex-1',\n assetsActive ? 'flex flex-col' : 'overflow-y-auto',\n collapsed && 'hidden',\n )}\n >\n {assetsActive ? null : activePane ? (\n activePane.render(paneContext)\n ) : (\n <NoOpenPane panes={allPanes} onOpen={openPane} />\n )}\n {assetsPane ? (\n <div\n key={SESSION_WORKBENCH_ASSETS_PANE_ID}\n data-workbench-assets=\"\"\n className={cn(assetsActive ? 'flex min-h-0 flex-1 flex-col' : 'hidden')}\n >\n {assetsPane.render(paneContext)}\n </div>\n ) : null}\n </div>\n </aside>\n </SessionWorkbenchContext.Provider>\n );\n};\n\nconst PaneMenuItems: FC<{\n panes: readonly SessionWorkbenchPane[];\n openIds: readonly string[];\n onOpen: (paneId: string) => void;\n}> = ({ panes, openIds, onOpen }) => (\n <ul className=\"flex flex-col\">\n {panes.map((pane) => {\n const Icon = pane.icon ?? LayersIcon;\n const isOpen = openIds.includes(pane.id);\n return (\n <li key={pane.id}>\n <button\n type=\"button\"\n title={pane.description}\n onClick={() => onOpen(pane.id)}\n className=\"flex h-[30px] w-full items-center gap-2 rounded-md px-2 text-left text-[13px] transition-colors hover:bg-muted focus-visible:bg-muted focus-visible:outline-none\"\n >\n <Icon className=\"size-4 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate\">{pane.title}</span>\n {isOpen ? <span className=\"text-[11px] text-muted-foreground\">Open</span> : null}\n </button>\n </li>\n );\n })}\n </ul>\n);\n\nconst AddPaneMenu: FC<{\n panes: readonly SessionWorkbenchPane[];\n openIds: readonly string[];\n onOpen: (paneId: string) => void;\n}> = ({ panes, openIds, onOpen }) => {\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState('');\n const shown = filterWorkbenchPanes(panes, query);\n const choose = (paneId: string) => {\n onOpen(paneId);\n setOpen(false);\n setQuery('');\n };\n return (\n <Popover\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) setQuery('');\n }}\n >\n <PopoverTrigger asChild>\n <button type=\"button\" aria-label=\"Open a pane\" title=\"Open a pane\" className={ICON_BUTTON_CLASS}>\n <PlusIcon className=\"size-4\" />\n </button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-56 p-1\" aria-label=\"Panes\">\n <div className=\"flex items-center gap-2 border-border border-b px-2 pb-1.5 pt-1\">\n <SearchIcon className=\"size-3.5 shrink-0 text-muted-foreground\" aria-hidden />\n <input\n // biome-ignore lint/a11y/noAutofocus: the menu opens to type into, as a command menu does.\n autoFocus\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onKeyDown={(event) => {\n const first = shown[0];\n if (event.key === 'Enter' && first) {\n event.preventDefault();\n choose(first.id);\n }\n }}\n placeholder=\"Search\"\n aria-label=\"Search panes\"\n className=\"h-7 min-w-0 flex-1 bg-transparent text-[13px] outline-none placeholder:text-muted-foreground\"\n />\n </div>\n <div className=\"pt-1\">\n {shown.length > 0 ? (\n <PaneMenuItems panes={shown} openIds={openIds} onOpen={choose} />\n ) : (\n <p className=\"px-2 py-1.5 text-[13px] text-muted-foreground\">No pane matches.</p>\n )}\n </div>\n </PopoverContent>\n </Popover>\n );\n};\n\n/** Nothing open: every pane the \"+\" menu offers, with what each shows, centred in the column. */\nconst NoOpenPane: FC<{\n panes: readonly SessionWorkbenchPane[];\n onOpen: (paneId: string) => void;\n}> = ({ panes, onOpen }) => (\n <div className=\"flex min-h-full items-center justify-center p-6\">\n <ul aria-label=\"Available panes\" className=\"flex w-full max-w-xs flex-col gap-1\">\n {panes.map((pane) => {\n const Icon = pane.icon ?? LayersIcon;\n return (\n <li key={pane.id}>\n <button\n type=\"button\"\n onClick={() => onOpen(pane.id)}\n className=\"flex w-full items-start gap-3 rounded-lg px-3 py-2 text-left transition-colors hover:bg-muted focus-visible:bg-muted focus-visible:outline-none\"\n >\n <Icon className=\"mt-0.5 size-4 shrink-0 text-muted-foreground\" />\n <span className=\"flex min-w-0 flex-col\">\n <span className=\"text-[13px] text-foreground\">{pane.title}</span>\n {pane.description ? (\n <span className=\"text-muted-foreground text-xs\">{pane.description}</span>\n ) : null}\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n);\n"],"names":["LayersIcon","jsx","AssetPanel","useAthenaQueryClient","QueryClientProvider","DEFAULT_SESSION_WORKBENCH_PANES","DEFAULT_SESSION_WORKBENCH_OPEN_PANE_IDS","useWorkbenchLayout","useState","useContext","AthenaStatewireThreadIdContext","useAthenaStatewireThreadList","useAuiState","readDeepAgentStreamValues","useMemo","useQueryClient","useRef","useEffect","SESSION_WORKSPACE_QUERY_KEY","useAssetPanelStore","useReducer","workbenchTabsReducer","useLayoutEffect","visibleThreadTabs","threadTabs","useId","SessionWorkbenchContext","jsxs","cn","Fragment","PanelRightOpenIcon","XIcon","Minimize2Icon","Maximize2Icon","PanelRightCloseIcon","filterWorkbenchPanes","Popover","PopoverTrigger","PlusIcon","PopoverContent","SearchIcon"],"mappings":"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"}
|
|
@@ -58,8 +58,8 @@ const SessionWorkbenchInner = ({
|
|
|
58
58
|
);
|
|
59
59
|
const isPersisted = threadList ? !threadList.isNewChat : sessionPersisted === true || hasMessages;
|
|
60
60
|
const paneContext = useMemo(
|
|
61
|
-
() => ({ threadId, isPersisted, isRunning }),
|
|
62
|
-
[threadId, isPersisted, isRunning]
|
|
61
|
+
() => ({ threadId, isPersisted, isRunning, isVisible: !collapsed }),
|
|
62
|
+
[threadId, isPersisted, isRunning, collapsed]
|
|
63
63
|
);
|
|
64
64
|
const queryClient = useQueryClient();
|
|
65
65
|
const wasRunning = useRef(isRunning);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionWorkbench.js","sources":["../../src/workbench/SessionWorkbench.tsx"],"sourcesContent":["\"use client\";\n\nimport { useAuiState } from '@assistant-ui/react';\nimport { readDeepAgentStreamValues } from '@athenaintel/agent-runtime';\nimport { QueryClientProvider, useQueryClient } from '@tanstack/react-query';\nimport {\n LayersIcon,\n Maximize2Icon,\n Minimize2Icon,\n PanelRightCloseIcon,\n PanelRightOpenIcon,\n PlusIcon,\n SearchIcon,\n XIcon,\n} from 'lucide-react';\nimport {\n type FC,\n type KeyboardEvent,\n useContext,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useReducer,\n useRef,\n useState,\n} from 'react';\nimport { useAssetPanelStore } from '../assets/asset-panel-store';\nimport { AssetPanel } from '../assets/AssetPanel';\nimport { useAthenaQueryClient } from '../lib/use-athena-rest-query';\nimport { cn } from '../lib/utils';\nimport { useAthenaStatewireThreadList } from '../threads/statewire-thread-list';\nimport { AthenaStatewireThreadIdContext } from '../threads/thread-id-context';\nimport { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';\nimport {\n type SessionWorkbenchPane,\n type SessionWorkbenchPaneContext,\n SessionWorkbenchContext,\n} from './session-workbench-context';\nimport {\n filterWorkbenchPanes,\n threadTabs,\n visibleThreadTabs,\n type WorkbenchThreadTabs,\n workbenchTabsReducer,\n} from './session-workbench-model';\nimport {\n DEFAULT_SESSION_WORKBENCH_OPEN_PANE_IDS,\n DEFAULT_SESSION_WORKBENCH_PANES,\n} from './session-workbench-panes';\nimport { SESSION_WORKSPACE_QUERY_KEY } from './use-session-workspace';\nimport { useWorkbenchLayout } from './workbench-layout-context';\n\n/** The tab `AthenaLayout`'s workbench shows the open assets in. */\nexport const SESSION_WORKBENCH_ASSETS_PANE_ID = 'open-assets';\n\nconst OPEN_ASSETS_PANE: SessionWorkbenchPane = {\n id: SESSION_WORKBENCH_ASSETS_PANE_ID,\n title: 'Open assets',\n description: 'The documents, sheets and decks open in this session',\n icon: LayersIcon,\n render: () => <AssetPanel manageAssetPanelHostRegistration={false} />,\n};\n\nexport interface SessionWorkbenchProps {\n /**\n * The panes the \"+\" menu offers, in order. Defaults to the built-in Tasks,\n * Shell, Computer, Pull requests and Attachments\n * (`DEFAULT_SESSION_WORKBENCH_PANES`); spread it to add your own.\n */\n panes?: readonly SessionWorkbenchPane[];\n /**\n * The panes a thread opens with until the person changes its tabs (ids from\n * `panes`; unknown ids are ignored). Defaults to Tasks, Shell and Computer.\n */\n defaultOpenPaneIds?: readonly string[];\n className?: string;\n}\n\nconst ICON_BUTTON_CLASS =\n 'inline-flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring/60';\n\n/**\n * A Devin-style workspace pane for the active session: a tab strip of panes\n * (Tasks, Shell, Computer, Pull requests, Attachments — and any the host\n * adds), a \"+\" menu to open more, closable tabs, and a collapse to a rail.\n *\n * Mount it inside `<AthenaProvider>` — as `AthenaLayout`'s `workbench` for a\n * resizable right-hand column, or anywhere in your own layout. Each thread\n * keeps its own open tabs, and whether the workspace is open or collapsed,\n * while the workbench stays mounted (in memory only); a thread it has not seen\n * opens `defaultOpenPaneIds`, collapsed when the layout's\n * `defaultWorkbenchCollapsed` says so. In `AthenaLayout`, the assets the agent\n * or the person opens show here too, as an \"Open assets\" tab.\n */\nexport const SessionWorkbench: FC<SessionWorkbenchProps> = (props) => {\n // One client for every pane, so the workspace read several panes share is\n // fetched once even when the host provides no QueryClient.\n const queryClient = useAthenaQueryClient();\n return (\n <QueryClientProvider client={queryClient}>\n <SessionWorkbenchInner {...props} />\n </QueryClientProvider>\n );\n};\n\nconst SessionWorkbenchInner: FC<SessionWorkbenchProps> = ({\n panes = DEFAULT_SESSION_WORKBENCH_PANES,\n defaultOpenPaneIds = DEFAULT_SESSION_WORKBENCH_OPEN_PANE_IDS,\n className,\n}) => {\n const layout = useWorkbenchLayout();\n const [ownCollapsed, setOwnCollapsed] = useState(false);\n const collapsed = layout ? layout.collapsed : ownCollapsed;\n const setCollapsed = layout ? layout.setCollapsed : setOwnCollapsed;\n // Only a hosting layout can give the workbench the chat's width.\n const maximized = layout?.maximized ?? false;\n const setMaximized = layout?.setMaximized;\n const hostsAssetPanel = layout?.hostsAssetPanel ?? false;\n\n const threadId = useContext(AthenaStatewireThreadIdContext);\n const threadList = useAthenaStatewireThreadList();\n const hasMessages = useAuiState((state) => state.thread.messages.length > 0);\n // The attach snapshot says whether the backend minted the thread's session\n // (`sessionPersisted`) — true for an existing thread before any message.\n const sessionPersisted = useAuiState(\n (state) =>\n readDeepAgentStreamValues((state.thread.extras as { state?: unknown } | undefined)?.state)\n .sessionPersisted,\n );\n const isRunning = useAuiState(\n (state) => (state.thread.extras as { status?: unknown } | undefined)?.status === 'running',\n );\n // In thread-list mode the list knows whether the backend has the thread; a\n // single-thread provider's thread is known once the host says its session\n // exists, or once it has history (a host that predates the flag).\n const isPersisted = threadList\n ? !threadList.isNewChat\n : sessionPersisted === true || hasMessages;\n const paneContext = useMemo<SessionWorkbenchPaneContext>(\n () => ({ threadId, isPersisted, isRunning }),\n [threadId, isPersisted, isRunning],\n );\n\n // Polling stops with the run; read the workspace once more as it settles so\n // what the run's last steps created shows without waiting for a refocus.\n const queryClient = useQueryClient();\n const wasRunning = useRef(isRunning);\n useEffect(() => {\n if (wasRunning.current && !isRunning) {\n void queryClient.invalidateQueries({ queryKey: SESSION_WORKSPACE_QUERY_KEY });\n }\n wasRunning.current = isRunning;\n }, [isRunning, queryClient]);\n\n const assetPanelOpen = useAssetPanelStore((state) => state.isOpen);\n const closeAssetPanel = useAssetPanelStore((state) => state.closePanel);\n const allPanes = useMemo<readonly SessionWorkbenchPane[]>(\n () => (hostsAssetPanel && assetPanelOpen ? [...panes, OPEN_ASSETS_PANE] : panes),\n [assetPanelOpen, hostsAssetPanel, panes],\n );\n const paneById = useMemo(() => new Map(allPanes.map((pane) => [pane.id, pane])), [allPanes]);\n const paneIds = useMemo(() => new Set(paneById.keys()), [paneById]);\n\n const defaults = useMemo<WorkbenchThreadTabs>(() => {\n const open = defaultOpenPaneIds.filter((id) => panes.some((pane) => pane.id === id));\n return { open, active: open[0] ?? null };\n }, [defaultOpenPaneIds, panes]);\n const [tabsState, dispatchTabs] = useReducer(workbenchTabsReducer, {});\n const threadKey = threadId ?? '';\n\n // Whether the workspace is open is kept per thread, like its tabs: a thread\n // the person has not opened or collapsed it in shows the layout's default (a\n // new session, or one switched to), so one session's open workspace never\n // follows the person into the next. Applied before paint, so a switch never\n // flashes the previous thread's state.\n const defaultCollapsed = layout?.defaultCollapsed ?? false;\n const collapsedByThread = useRef(new Map<string, boolean>());\n const setThreadCollapsed = (next: boolean) => {\n collapsedByThread.current.set(threadKey, next);\n setCollapsed(next);\n };\n useLayoutEffect(() => {\n setCollapsed(collapsedByThread.current.get(threadKey) ?? defaultCollapsed);\n }, [threadKey, defaultCollapsed, setCollapsed]);\n let tabs = visibleThreadTabs(threadTabs(tabsState, threadKey, defaults), paneIds);\n // Assets already open when the workbench mounts (or when a thread with open\n // assets comes back) show their tab without an opening to react to.\n if (\n paneIds.has(SESSION_WORKBENCH_ASSETS_PANE_ID) &&\n !tabs.open.includes(SESSION_WORKBENCH_ASSETS_PANE_ID)\n ) {\n tabs = {\n open: [...tabs.open, SESSION_WORKBENCH_ASSETS_PANE_ID],\n active: tabs.active ?? SESSION_WORKBENCH_ASSETS_PANE_ID,\n };\n }\n\n // An asset opening (a tool card, a citation, the Attachments pane) brings\n // the \"Open assets\" tab forward and the workbench out of its rail. A thread\n // switch restoring that thread's open assets (`setCurrentThread`) is not an\n // opening: the thread keeps the pane it had selected. Read through refs:\n // the store notifies outside React's render.\n const latest = useRef({ threadKey, defaults, setCollapsed });\n latest.current = { threadKey, defaults, setCollapsed };\n useEffect(() => {\n if (!hostsAssetPanel) return undefined;\n return useAssetPanelStore.subscribe((state, previous) => {\n if (state.currentThreadId !== previous.currentThreadId) return;\n // Every open request, a repeat click on the asset already showing too.\n if (!state.isOpen || state.openRequestSeq === previous.openRequestSeq) return;\n dispatchTabs({\n type: 'open',\n threadKey: latest.current.threadKey,\n paneId: SESSION_WORKBENCH_ASSETS_PANE_ID,\n defaults: latest.current.defaults,\n });\n collapsedByThread.current.set(latest.current.threadKey, false);\n latest.current.setCollapsed(false);\n });\n }, [hostsAssetPanel]);\n\n // Showing a tab the strip renders is an `open`: it activates a stored tab\n // and records one the strip only derived (the \"Open assets\" tab above).\n const openPane = (paneId: string) =>\n dispatchTabs({ type: 'open', threadKey, paneId, defaults });\n const activatePane = openPane;\n const closePane = (paneId: string) => {\n if (paneId === SESSION_WORKBENCH_ASSETS_PANE_ID) closeAssetPanel();\n dispatchTabs({ type: 'close', threadKey, paneId, defaults });\n };\n\n const baseId = useId();\n const tabId = (paneId: string) => `${baseId}-tab-${paneId}`;\n const panelId = `${baseId}-panel`;\n\n const onTabKeyDown = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {\n const step = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0;\n if (step === 0 || tabs.open.length === 0) return;\n event.preventDefault();\n const nextId = tabs.open[(index + step + tabs.open.length) % tabs.open.length];\n if (!nextId) return;\n activatePane(nextId);\n event.currentTarget.ownerDocument.getElementById(tabId(nextId))?.focus();\n };\n\n const activePane = tabs.active ? paneById.get(tabs.active) : undefined;\n const assetsPane = paneById.get(SESSION_WORKBENCH_ASSETS_PANE_ID);\n const assetsActive = activePane?.id === SESSION_WORKBENCH_ASSETS_PANE_ID;\n\n // One tree for both states: the panel stays mounted (hidden) while the\n // workbench is a rail, and the open assets stay mounted (hidden) while\n // another pane shows — their iframes keep their editing session, as the\n // asset panel's own tabs do.\n return (\n <SessionWorkbenchContext.Provider value={paneContext}>\n <aside\n aria-label=\"Session workspace\"\n className={cn(\n 'athena-sdk-chrome flex h-full flex-col border-border border-l bg-background',\n collapsed ? 'w-10 shrink-0 items-center gap-1 py-2' : 'min-h-0 w-full min-w-0',\n className,\n )}\n >\n {collapsed ? (\n <>\n <button\n type=\"button\"\n aria-label=\"Expand workspace\"\n title=\"Expand workspace\"\n onClick={() => setThreadCollapsed(false)}\n className={ICON_BUTTON_CLASS}\n >\n <PanelRightOpenIcon className=\"size-4\" />\n </button>\n {tabs.open.map((paneId) => {\n const pane = paneById.get(paneId);\n if (!pane) return null;\n const Icon = pane.icon ?? LayersIcon;\n return (\n <button\n key={paneId}\n type=\"button\"\n aria-label={`Show ${pane.title}`}\n title={pane.title}\n onClick={() => {\n activatePane(paneId);\n setThreadCollapsed(false);\n }}\n className={cn(\n ICON_BUTTON_CLASS,\n paneId === tabs.active && 'bg-muted text-foreground',\n )}\n >\n <Icon className=\"size-4\" />\n </button>\n );\n })}\n </>\n ) : (\n <div className=\"flex h-11 w-full shrink-0 items-center gap-1 border-border border-b pr-1.5 pl-2\">\n <div\n role=\"tablist\"\n aria-label=\"Workspace panes\"\n className=\"flex min-w-0 items-center gap-1 overflow-x-auto [scrollbar-width:none]\"\n >\n {tabs.open.map((paneId, index) => {\n const pane = paneById.get(paneId);\n if (!pane) return null;\n const Icon = pane.icon;\n const selected = paneId === tabs.active;\n return (\n <div\n key={paneId}\n data-selected={selected ? '' : undefined}\n className={cn(\n 'group/tab flex h-7 shrink-0 items-center rounded-md text-[13px] transition-colors',\n selected\n ? 'bg-foreground/[0.06] text-foreground'\n : 'text-muted-foreground hover:bg-foreground/[0.04] hover:text-foreground',\n )}\n >\n <button\n type=\"button\"\n role=\"tab\"\n id={tabId(paneId)}\n aria-selected={selected}\n aria-controls={panelId}\n tabIndex={selected ? 0 : -1}\n onClick={() => activatePane(paneId)}\n onKeyDown={(event) => onTabKeyDown(event, index)}\n className=\"flex h-full items-center gap-1.5 rounded-md pr-1 pl-2 outline-none focus-visible:ring-2 focus-visible:ring-ring/60\"\n >\n {Icon ? <Icon className=\"size-3.5\" /> : null}\n {pane.title}\n </button>\n {/* Quiet until the tab is hovered, focused or selected, as in Devin's strip. */}\n <button\n type=\"button\"\n aria-label={`Close ${pane.title}`}\n title={`Close ${pane.title}`}\n onClick={() => closePane(paneId)}\n className={cn(\n 'mr-1 rounded p-0.5 transition-opacity hover:bg-background hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/60',\n selected ? 'opacity-60' : 'opacity-0 group-hover/tab:opacity-60',\n )}\n >\n <XIcon className=\"size-3\" />\n </button>\n </div>\n );\n })}\n </div>\n <AddPaneMenu panes={allPanes} openIds={tabs.open} onOpen={openPane} />\n <div className=\"min-w-0 flex-1\" />\n {setMaximized ? (\n <button\n type=\"button\"\n aria-label={maximized ? 'Restore workspace' : 'Maximize workspace'}\n title={maximized ? 'Restore workspace' : 'Maximize workspace'}\n aria-pressed={maximized}\n onClick={() => setMaximized(!maximized)}\n className={ICON_BUTTON_CLASS}\n >\n {maximized ? <Minimize2Icon className=\"size-4\" /> : <Maximize2Icon className=\"size-4\" />}\n </button>\n ) : null}\n <button\n type=\"button\"\n aria-label=\"Collapse workspace\"\n title=\"Collapse workspace\"\n onClick={() => setThreadCollapsed(true)}\n className={ICON_BUTTON_CLASS}\n >\n <PanelRightCloseIcon className=\"size-4\" />\n </button>\n </div>\n )}\n <div\n role=\"tabpanel\"\n id={panelId}\n aria-labelledby={activePane ? tabId(activePane.id) : undefined}\n className={cn(\n 'min-h-0 w-full flex-1',\n assetsActive ? 'flex flex-col' : 'overflow-y-auto',\n collapsed && 'hidden',\n )}\n >\n {assetsActive ? null : activePane ? (\n activePane.render(paneContext)\n ) : (\n <NoOpenPane panes={allPanes} onOpen={openPane} />\n )}\n {assetsPane ? (\n <div\n key={SESSION_WORKBENCH_ASSETS_PANE_ID}\n data-workbench-assets=\"\"\n className={cn(assetsActive ? 'flex min-h-0 flex-1 flex-col' : 'hidden')}\n >\n {assetsPane.render(paneContext)}\n </div>\n ) : null}\n </div>\n </aside>\n </SessionWorkbenchContext.Provider>\n );\n};\n\nconst PaneMenuItems: FC<{\n panes: readonly SessionWorkbenchPane[];\n openIds: readonly string[];\n onOpen: (paneId: string) => void;\n}> = ({ panes, openIds, onOpen }) => (\n <ul className=\"flex flex-col\">\n {panes.map((pane) => {\n const Icon = pane.icon ?? LayersIcon;\n const isOpen = openIds.includes(pane.id);\n return (\n <li key={pane.id}>\n <button\n type=\"button\"\n title={pane.description}\n onClick={() => onOpen(pane.id)}\n className=\"flex h-[30px] w-full items-center gap-2 rounded-md px-2 text-left text-[13px] transition-colors hover:bg-muted focus-visible:bg-muted focus-visible:outline-none\"\n >\n <Icon className=\"size-4 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate\">{pane.title}</span>\n {isOpen ? <span className=\"text-[11px] text-muted-foreground\">Open</span> : null}\n </button>\n </li>\n );\n })}\n </ul>\n);\n\nconst AddPaneMenu: FC<{\n panes: readonly SessionWorkbenchPane[];\n openIds: readonly string[];\n onOpen: (paneId: string) => void;\n}> = ({ panes, openIds, onOpen }) => {\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState('');\n const shown = filterWorkbenchPanes(panes, query);\n const choose = (paneId: string) => {\n onOpen(paneId);\n setOpen(false);\n setQuery('');\n };\n return (\n <Popover\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) setQuery('');\n }}\n >\n <PopoverTrigger asChild>\n <button type=\"button\" aria-label=\"Open a pane\" title=\"Open a pane\" className={ICON_BUTTON_CLASS}>\n <PlusIcon className=\"size-4\" />\n </button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-56 p-1\" aria-label=\"Panes\">\n <div className=\"flex items-center gap-2 border-border border-b px-2 pb-1.5 pt-1\">\n <SearchIcon className=\"size-3.5 shrink-0 text-muted-foreground\" aria-hidden />\n <input\n // biome-ignore lint/a11y/noAutofocus: the menu opens to type into, as a command menu does.\n autoFocus\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onKeyDown={(event) => {\n const first = shown[0];\n if (event.key === 'Enter' && first) {\n event.preventDefault();\n choose(first.id);\n }\n }}\n placeholder=\"Search\"\n aria-label=\"Search panes\"\n className=\"h-7 min-w-0 flex-1 bg-transparent text-[13px] outline-none placeholder:text-muted-foreground\"\n />\n </div>\n <div className=\"pt-1\">\n {shown.length > 0 ? (\n <PaneMenuItems panes={shown} openIds={openIds} onOpen={choose} />\n ) : (\n <p className=\"px-2 py-1.5 text-[13px] text-muted-foreground\">No pane matches.</p>\n )}\n </div>\n </PopoverContent>\n </Popover>\n );\n};\n\n/** Nothing open: every pane the \"+\" menu offers, with what each shows, centred in the column. */\nconst NoOpenPane: FC<{\n panes: readonly SessionWorkbenchPane[];\n onOpen: (paneId: string) => void;\n}> = ({ panes, onOpen }) => (\n <div className=\"flex min-h-full items-center justify-center p-6\">\n <ul aria-label=\"Available panes\" className=\"flex w-full max-w-xs flex-col gap-1\">\n {panes.map((pane) => {\n const Icon = pane.icon ?? LayersIcon;\n return (\n <li key={pane.id}>\n <button\n type=\"button\"\n onClick={() => onOpen(pane.id)}\n className=\"flex w-full items-start gap-3 rounded-lg px-3 py-2 text-left transition-colors hover:bg-muted focus-visible:bg-muted focus-visible:outline-none\"\n >\n <Icon className=\"mt-0.5 size-4 shrink-0 text-muted-foreground\" />\n <span className=\"flex min-w-0 flex-col\">\n <span className=\"text-[13px] text-foreground\">{pane.title}</span>\n {pane.description ? (\n <span className=\"text-muted-foreground text-xs\">{pane.description}</span>\n ) : null}\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n);\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SessionWorkbench.js","sources":["../../src/workbench/SessionWorkbench.tsx"],"sourcesContent":["\"use client\";\n\nimport { useAuiState } from '@assistant-ui/react';\nimport { readDeepAgentStreamValues } from '@athenaintel/agent-runtime';\nimport { QueryClientProvider, useQueryClient } from '@tanstack/react-query';\nimport {\n LayersIcon,\n Maximize2Icon,\n Minimize2Icon,\n PanelRightCloseIcon,\n PanelRightOpenIcon,\n PlusIcon,\n SearchIcon,\n XIcon,\n} from 'lucide-react';\nimport {\n type FC,\n type KeyboardEvent,\n useContext,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useReducer,\n useRef,\n useState,\n} from 'react';\nimport { useAssetPanelStore } from '../assets/asset-panel-store';\nimport { AssetPanel } from '../assets/AssetPanel';\nimport { useAthenaQueryClient } from '../lib/use-athena-rest-query';\nimport { cn } from '../lib/utils';\nimport { useAthenaStatewireThreadList } from '../threads/statewire-thread-list';\nimport { AthenaStatewireThreadIdContext } from '../threads/thread-id-context';\nimport { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';\nimport {\n type SessionWorkbenchPane,\n type SessionWorkbenchPaneContext,\n SessionWorkbenchContext,\n} from './session-workbench-context';\nimport {\n filterWorkbenchPanes,\n threadTabs,\n visibleThreadTabs,\n type WorkbenchThreadTabs,\n workbenchTabsReducer,\n} from './session-workbench-model';\nimport {\n DEFAULT_SESSION_WORKBENCH_OPEN_PANE_IDS,\n DEFAULT_SESSION_WORKBENCH_PANES,\n} from './session-workbench-panes';\nimport { SESSION_WORKSPACE_QUERY_KEY } from './use-session-workspace';\nimport { useWorkbenchLayout } from './workbench-layout-context';\n\n/** The tab `AthenaLayout`'s workbench shows the open assets in. */\nexport const SESSION_WORKBENCH_ASSETS_PANE_ID = 'open-assets';\n\nconst OPEN_ASSETS_PANE: SessionWorkbenchPane = {\n id: SESSION_WORKBENCH_ASSETS_PANE_ID,\n title: 'Open assets',\n description: 'The documents, sheets and decks open in this session',\n icon: LayersIcon,\n render: () => <AssetPanel manageAssetPanelHostRegistration={false} />,\n};\n\nexport interface SessionWorkbenchProps {\n /**\n * The panes the \"+\" menu offers, in order. Defaults to the built-in Tasks,\n * Shell, Computer, Pull requests and Attachments\n * (`DEFAULT_SESSION_WORKBENCH_PANES`); spread it to add your own.\n */\n panes?: readonly SessionWorkbenchPane[];\n /**\n * The panes a thread opens with until the person changes its tabs (ids from\n * `panes`; unknown ids are ignored). Defaults to Tasks, Shell and Computer.\n */\n defaultOpenPaneIds?: readonly string[];\n className?: string;\n}\n\nconst ICON_BUTTON_CLASS =\n 'inline-flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring/60';\n\n/**\n * A Devin-style workspace pane for the active session: a tab strip of panes\n * (Tasks, Shell, Computer, Pull requests, Attachments — and any the host\n * adds), a \"+\" menu to open more, closable tabs, and a collapse to a rail.\n *\n * Mount it inside `<AthenaProvider>` — as `AthenaLayout`'s `workbench` for a\n * resizable right-hand column, or anywhere in your own layout. Each thread\n * keeps its own open tabs, and whether the workspace is open or collapsed,\n * while the workbench stays mounted (in memory only); a thread it has not seen\n * opens `defaultOpenPaneIds`, collapsed when the layout's\n * `defaultWorkbenchCollapsed` says so. In `AthenaLayout`, the assets the agent\n * or the person opens show here too, as an \"Open assets\" tab.\n */\nexport const SessionWorkbench: FC<SessionWorkbenchProps> = (props) => {\n // One client for every pane, so the workspace read several panes share is\n // fetched once even when the host provides no QueryClient.\n const queryClient = useAthenaQueryClient();\n return (\n <QueryClientProvider client={queryClient}>\n <SessionWorkbenchInner {...props} />\n </QueryClientProvider>\n );\n};\n\nconst SessionWorkbenchInner: FC<SessionWorkbenchProps> = ({\n panes = DEFAULT_SESSION_WORKBENCH_PANES,\n defaultOpenPaneIds = DEFAULT_SESSION_WORKBENCH_OPEN_PANE_IDS,\n className,\n}) => {\n const layout = useWorkbenchLayout();\n const [ownCollapsed, setOwnCollapsed] = useState(false);\n const collapsed = layout ? layout.collapsed : ownCollapsed;\n const setCollapsed = layout ? layout.setCollapsed : setOwnCollapsed;\n // Only a hosting layout can give the workbench the chat's width.\n const maximized = layout?.maximized ?? false;\n const setMaximized = layout?.setMaximized;\n const hostsAssetPanel = layout?.hostsAssetPanel ?? false;\n\n const threadId = useContext(AthenaStatewireThreadIdContext);\n const threadList = useAthenaStatewireThreadList();\n const hasMessages = useAuiState((state) => state.thread.messages.length > 0);\n // The attach snapshot says whether the backend minted the thread's session\n // (`sessionPersisted`) — true for an existing thread before any message.\n const sessionPersisted = useAuiState(\n (state) =>\n readDeepAgentStreamValues((state.thread.extras as { state?: unknown } | undefined)?.state)\n .sessionPersisted,\n );\n const isRunning = useAuiState(\n (state) => (state.thread.extras as { status?: unknown } | undefined)?.status === 'running',\n );\n // In thread-list mode the list knows whether the backend has the thread; a\n // single-thread provider's thread is known once the host says its session\n // exists, or once it has history (a host that predates the flag).\n const isPersisted = threadList\n ? !threadList.isNewChat\n : sessionPersisted === true || hasMessages;\n const paneContext = useMemo<SessionWorkbenchPaneContext>(\n () => ({ threadId, isPersisted, isRunning, isVisible: !collapsed }),\n [threadId, isPersisted, isRunning, collapsed],\n );\n\n // Polling stops with the run; read the workspace once more as it settles so\n // what the run's last steps created shows without waiting for a refocus.\n const queryClient = useQueryClient();\n const wasRunning = useRef(isRunning);\n useEffect(() => {\n if (wasRunning.current && !isRunning) {\n void queryClient.invalidateQueries({ queryKey: SESSION_WORKSPACE_QUERY_KEY });\n }\n wasRunning.current = isRunning;\n }, [isRunning, queryClient]);\n\n const assetPanelOpen = useAssetPanelStore((state) => state.isOpen);\n const closeAssetPanel = useAssetPanelStore((state) => state.closePanel);\n const allPanes = useMemo<readonly SessionWorkbenchPane[]>(\n () => (hostsAssetPanel && assetPanelOpen ? [...panes, OPEN_ASSETS_PANE] : panes),\n [assetPanelOpen, hostsAssetPanel, panes],\n );\n const paneById = useMemo(() => new Map(allPanes.map((pane) => [pane.id, pane])), [allPanes]);\n const paneIds = useMemo(() => new Set(paneById.keys()), [paneById]);\n\n const defaults = useMemo<WorkbenchThreadTabs>(() => {\n const open = defaultOpenPaneIds.filter((id) => panes.some((pane) => pane.id === id));\n return { open, active: open[0] ?? null };\n }, [defaultOpenPaneIds, panes]);\n const [tabsState, dispatchTabs] = useReducer(workbenchTabsReducer, {});\n const threadKey = threadId ?? '';\n\n // Whether the workspace is open is kept per thread, like its tabs: a thread\n // the person has not opened or collapsed it in shows the layout's default (a\n // new session, or one switched to), so one session's open workspace never\n // follows the person into the next. Applied before paint, so a switch never\n // flashes the previous thread's state.\n const defaultCollapsed = layout?.defaultCollapsed ?? false;\n const collapsedByThread = useRef(new Map<string, boolean>());\n const setThreadCollapsed = (next: boolean) => {\n collapsedByThread.current.set(threadKey, next);\n setCollapsed(next);\n };\n useLayoutEffect(() => {\n setCollapsed(collapsedByThread.current.get(threadKey) ?? defaultCollapsed);\n }, [threadKey, defaultCollapsed, setCollapsed]);\n let tabs = visibleThreadTabs(threadTabs(tabsState, threadKey, defaults), paneIds);\n // Assets already open when the workbench mounts (or when a thread with open\n // assets comes back) show their tab without an opening to react to.\n if (\n paneIds.has(SESSION_WORKBENCH_ASSETS_PANE_ID) &&\n !tabs.open.includes(SESSION_WORKBENCH_ASSETS_PANE_ID)\n ) {\n tabs = {\n open: [...tabs.open, SESSION_WORKBENCH_ASSETS_PANE_ID],\n active: tabs.active ?? SESSION_WORKBENCH_ASSETS_PANE_ID,\n };\n }\n\n // An asset opening (a tool card, a citation, the Attachments pane) brings\n // the \"Open assets\" tab forward and the workbench out of its rail. A thread\n // switch restoring that thread's open assets (`setCurrentThread`) is not an\n // opening: the thread keeps the pane it had selected. Read through refs:\n // the store notifies outside React's render.\n const latest = useRef({ threadKey, defaults, setCollapsed });\n latest.current = { threadKey, defaults, setCollapsed };\n useEffect(() => {\n if (!hostsAssetPanel) return undefined;\n return useAssetPanelStore.subscribe((state, previous) => {\n if (state.currentThreadId !== previous.currentThreadId) return;\n // Every open request, a repeat click on the asset already showing too.\n if (!state.isOpen || state.openRequestSeq === previous.openRequestSeq) return;\n dispatchTabs({\n type: 'open',\n threadKey: latest.current.threadKey,\n paneId: SESSION_WORKBENCH_ASSETS_PANE_ID,\n defaults: latest.current.defaults,\n });\n collapsedByThread.current.set(latest.current.threadKey, false);\n latest.current.setCollapsed(false);\n });\n }, [hostsAssetPanel]);\n\n // Showing a tab the strip renders is an `open`: it activates a stored tab\n // and records one the strip only derived (the \"Open assets\" tab above).\n const openPane = (paneId: string) =>\n dispatchTabs({ type: 'open', threadKey, paneId, defaults });\n const activatePane = openPane;\n const closePane = (paneId: string) => {\n if (paneId === SESSION_WORKBENCH_ASSETS_PANE_ID) closeAssetPanel();\n dispatchTabs({ type: 'close', threadKey, paneId, defaults });\n };\n\n const baseId = useId();\n const tabId = (paneId: string) => `${baseId}-tab-${paneId}`;\n const panelId = `${baseId}-panel`;\n\n const onTabKeyDown = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {\n const step = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0;\n if (step === 0 || tabs.open.length === 0) return;\n event.preventDefault();\n const nextId = tabs.open[(index + step + tabs.open.length) % tabs.open.length];\n if (!nextId) return;\n activatePane(nextId);\n event.currentTarget.ownerDocument.getElementById(tabId(nextId))?.focus();\n };\n\n const activePane = tabs.active ? paneById.get(tabs.active) : undefined;\n const assetsPane = paneById.get(SESSION_WORKBENCH_ASSETS_PANE_ID);\n const assetsActive = activePane?.id === SESSION_WORKBENCH_ASSETS_PANE_ID;\n\n // One tree for both states: the panel stays mounted (hidden) while the\n // workbench is a rail, and the open assets stay mounted (hidden) while\n // another pane shows — their iframes keep their editing session, as the\n // asset panel's own tabs do.\n return (\n <SessionWorkbenchContext.Provider value={paneContext}>\n <aside\n aria-label=\"Session workspace\"\n className={cn(\n 'athena-sdk-chrome flex h-full flex-col border-border border-l bg-background',\n collapsed ? 'w-10 shrink-0 items-center gap-1 py-2' : 'min-h-0 w-full min-w-0',\n className,\n )}\n >\n {collapsed ? (\n <>\n <button\n type=\"button\"\n aria-label=\"Expand workspace\"\n title=\"Expand workspace\"\n onClick={() => setThreadCollapsed(false)}\n className={ICON_BUTTON_CLASS}\n >\n <PanelRightOpenIcon className=\"size-4\" />\n </button>\n {tabs.open.map((paneId) => {\n const pane = paneById.get(paneId);\n if (!pane) return null;\n const Icon = pane.icon ?? LayersIcon;\n return (\n <button\n key={paneId}\n type=\"button\"\n aria-label={`Show ${pane.title}`}\n title={pane.title}\n onClick={() => {\n activatePane(paneId);\n setThreadCollapsed(false);\n }}\n className={cn(\n ICON_BUTTON_CLASS,\n paneId === tabs.active && 'bg-muted text-foreground',\n )}\n >\n <Icon className=\"size-4\" />\n </button>\n );\n })}\n </>\n ) : (\n <div className=\"flex h-11 w-full shrink-0 items-center gap-1 border-border border-b pr-1.5 pl-2\">\n <div\n role=\"tablist\"\n aria-label=\"Workspace panes\"\n className=\"flex min-w-0 items-center gap-1 overflow-x-auto [scrollbar-width:none]\"\n >\n {tabs.open.map((paneId, index) => {\n const pane = paneById.get(paneId);\n if (!pane) return null;\n const Icon = pane.icon;\n const selected = paneId === tabs.active;\n return (\n <div\n key={paneId}\n data-selected={selected ? '' : undefined}\n className={cn(\n 'group/tab flex h-7 shrink-0 items-center rounded-md text-[13px] transition-colors',\n selected\n ? 'bg-foreground/[0.06] text-foreground'\n : 'text-muted-foreground hover:bg-foreground/[0.04] hover:text-foreground',\n )}\n >\n <button\n type=\"button\"\n role=\"tab\"\n id={tabId(paneId)}\n aria-selected={selected}\n aria-controls={panelId}\n tabIndex={selected ? 0 : -1}\n onClick={() => activatePane(paneId)}\n onKeyDown={(event) => onTabKeyDown(event, index)}\n className=\"flex h-full items-center gap-1.5 rounded-md pr-1 pl-2 outline-none focus-visible:ring-2 focus-visible:ring-ring/60\"\n >\n {Icon ? <Icon className=\"size-3.5\" /> : null}\n {pane.title}\n </button>\n {/* Quiet until the tab is hovered, focused or selected, as in Devin's strip. */}\n <button\n type=\"button\"\n aria-label={`Close ${pane.title}`}\n title={`Close ${pane.title}`}\n onClick={() => closePane(paneId)}\n className={cn(\n 'mr-1 rounded p-0.5 transition-opacity hover:bg-background hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/60',\n selected ? 'opacity-60' : 'opacity-0 group-hover/tab:opacity-60',\n )}\n >\n <XIcon className=\"size-3\" />\n </button>\n </div>\n );\n })}\n </div>\n <AddPaneMenu panes={allPanes} openIds={tabs.open} onOpen={openPane} />\n <div className=\"min-w-0 flex-1\" />\n {setMaximized ? (\n <button\n type=\"button\"\n aria-label={maximized ? 'Restore workspace' : 'Maximize workspace'}\n title={maximized ? 'Restore workspace' : 'Maximize workspace'}\n aria-pressed={maximized}\n onClick={() => setMaximized(!maximized)}\n className={ICON_BUTTON_CLASS}\n >\n {maximized ? <Minimize2Icon className=\"size-4\" /> : <Maximize2Icon className=\"size-4\" />}\n </button>\n ) : null}\n <button\n type=\"button\"\n aria-label=\"Collapse workspace\"\n title=\"Collapse workspace\"\n onClick={() => setThreadCollapsed(true)}\n className={ICON_BUTTON_CLASS}\n >\n <PanelRightCloseIcon className=\"size-4\" />\n </button>\n </div>\n )}\n <div\n role=\"tabpanel\"\n id={panelId}\n aria-labelledby={activePane ? tabId(activePane.id) : undefined}\n className={cn(\n 'min-h-0 w-full flex-1',\n assetsActive ? 'flex flex-col' : 'overflow-y-auto',\n collapsed && 'hidden',\n )}\n >\n {assetsActive ? null : activePane ? (\n activePane.render(paneContext)\n ) : (\n <NoOpenPane panes={allPanes} onOpen={openPane} />\n )}\n {assetsPane ? (\n <div\n key={SESSION_WORKBENCH_ASSETS_PANE_ID}\n data-workbench-assets=\"\"\n className={cn(assetsActive ? 'flex min-h-0 flex-1 flex-col' : 'hidden')}\n >\n {assetsPane.render(paneContext)}\n </div>\n ) : null}\n </div>\n </aside>\n </SessionWorkbenchContext.Provider>\n );\n};\n\nconst PaneMenuItems: FC<{\n panes: readonly SessionWorkbenchPane[];\n openIds: readonly string[];\n onOpen: (paneId: string) => void;\n}> = ({ panes, openIds, onOpen }) => (\n <ul className=\"flex flex-col\">\n {panes.map((pane) => {\n const Icon = pane.icon ?? LayersIcon;\n const isOpen = openIds.includes(pane.id);\n return (\n <li key={pane.id}>\n <button\n type=\"button\"\n title={pane.description}\n onClick={() => onOpen(pane.id)}\n className=\"flex h-[30px] w-full items-center gap-2 rounded-md px-2 text-left text-[13px] transition-colors hover:bg-muted focus-visible:bg-muted focus-visible:outline-none\"\n >\n <Icon className=\"size-4 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate\">{pane.title}</span>\n {isOpen ? <span className=\"text-[11px] text-muted-foreground\">Open</span> : null}\n </button>\n </li>\n );\n })}\n </ul>\n);\n\nconst AddPaneMenu: FC<{\n panes: readonly SessionWorkbenchPane[];\n openIds: readonly string[];\n onOpen: (paneId: string) => void;\n}> = ({ panes, openIds, onOpen }) => {\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState('');\n const shown = filterWorkbenchPanes(panes, query);\n const choose = (paneId: string) => {\n onOpen(paneId);\n setOpen(false);\n setQuery('');\n };\n return (\n <Popover\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) setQuery('');\n }}\n >\n <PopoverTrigger asChild>\n <button type=\"button\" aria-label=\"Open a pane\" title=\"Open a pane\" className={ICON_BUTTON_CLASS}>\n <PlusIcon className=\"size-4\" />\n </button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-56 p-1\" aria-label=\"Panes\">\n <div className=\"flex items-center gap-2 border-border border-b px-2 pb-1.5 pt-1\">\n <SearchIcon className=\"size-3.5 shrink-0 text-muted-foreground\" aria-hidden />\n <input\n // biome-ignore lint/a11y/noAutofocus: the menu opens to type into, as a command menu does.\n autoFocus\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onKeyDown={(event) => {\n const first = shown[0];\n if (event.key === 'Enter' && first) {\n event.preventDefault();\n choose(first.id);\n }\n }}\n placeholder=\"Search\"\n aria-label=\"Search panes\"\n className=\"h-7 min-w-0 flex-1 bg-transparent text-[13px] outline-none placeholder:text-muted-foreground\"\n />\n </div>\n <div className=\"pt-1\">\n {shown.length > 0 ? (\n <PaneMenuItems panes={shown} openIds={openIds} onOpen={choose} />\n ) : (\n <p className=\"px-2 py-1.5 text-[13px] text-muted-foreground\">No pane matches.</p>\n )}\n </div>\n </PopoverContent>\n </Popover>\n );\n};\n\n/** Nothing open: every pane the \"+\" menu offers, with what each shows, centred in the column. */\nconst NoOpenPane: FC<{\n panes: readonly SessionWorkbenchPane[];\n onOpen: (paneId: string) => void;\n}> = ({ panes, onOpen }) => (\n <div className=\"flex min-h-full items-center justify-center p-6\">\n <ul aria-label=\"Available panes\" className=\"flex w-full max-w-xs flex-col gap-1\">\n {panes.map((pane) => {\n const Icon = pane.icon ?? LayersIcon;\n return (\n <li key={pane.id}>\n <button\n type=\"button\"\n onClick={() => onOpen(pane.id)}\n className=\"flex w-full items-start gap-3 rounded-lg px-3 py-2 text-left transition-colors hover:bg-muted focus-visible:bg-muted focus-visible:outline-none\"\n >\n <Icon className=\"mt-0.5 size-4 shrink-0 text-muted-foreground\" />\n <span className=\"flex min-w-0 flex-col\">\n <span className=\"text-[13px] text-foreground\">{pane.title}</span>\n {pane.description ? (\n <span className=\"text-muted-foreground text-xs\">{pane.description}</span>\n ) : null}\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n);\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const mint = require("../collab/mint.cjs");
|
|
4
|
+
const errors = require("../diagnostics/errors.cjs");
|
|
5
|
+
const utils = require("../lib/utils.cjs");
|
|
6
|
+
const credentialRenewal = require("../runtime/credential-renewal.cjs");
|
|
7
|
+
const LiveDesktopMode = { WATCH: "watch", CONTROL: "control" };
|
|
8
|
+
function computerLiveDesktopSessionPath(assetId) {
|
|
9
|
+
return `/api/sandbox/computer/${encodeURIComponent(assetId)}/live-desktop/session`;
|
|
10
|
+
}
|
|
11
|
+
function readDisplay(value) {
|
|
12
|
+
if (!utils.isRecord(value)) return null;
|
|
13
|
+
const { width, height } = value;
|
|
14
|
+
return typeof width === "number" && width > 0 && typeof height === "number" && height > 0 ? { width, height } : null;
|
|
15
|
+
}
|
|
16
|
+
function readLiveDesktopSession(body) {
|
|
17
|
+
if (!utils.isRecord(body)) return null;
|
|
18
|
+
const detail = typeof body.detail === "string" ? body.detail : "";
|
|
19
|
+
if (body.status === "display_not_running") return { status: "display_not_running", detail };
|
|
20
|
+
if (body.status === "unsupported") return { status: "unsupported", detail };
|
|
21
|
+
if (body.status !== "ready") return null;
|
|
22
|
+
const { viewer_path: viewerPath, mode } = body;
|
|
23
|
+
if (typeof viewerPath !== "string" || !/^\/(?!\/)/.test(viewerPath)) return null;
|
|
24
|
+
if (mode !== LiveDesktopMode.WATCH && mode !== LiveDesktopMode.CONTROL) return null;
|
|
25
|
+
return { status: "ready", viewerPath, mode, display: readDisplay(body.display) };
|
|
26
|
+
}
|
|
27
|
+
function liveDesktopViewerUrl({
|
|
28
|
+
backendUrl,
|
|
29
|
+
viewerPath
|
|
30
|
+
}) {
|
|
31
|
+
const base = new URL(`${mint.getAthenaApiBaseUrl(backendUrl)}/`);
|
|
32
|
+
const url = new URL(viewerPath, base);
|
|
33
|
+
if (url.origin !== base.origin) {
|
|
34
|
+
throw new Error("The live desktop viewer must be served by the Athena API");
|
|
35
|
+
}
|
|
36
|
+
return url.toString();
|
|
37
|
+
}
|
|
38
|
+
function refusalDetail(body) {
|
|
39
|
+
if (!utils.isRecord(body)) return null;
|
|
40
|
+
return typeof body.detail === "string" && body.detail ? body.detail : null;
|
|
41
|
+
}
|
|
42
|
+
async function createComputerLiveDesktopSession({
|
|
43
|
+
backendUrl,
|
|
44
|
+
auth,
|
|
45
|
+
assetId,
|
|
46
|
+
mode,
|
|
47
|
+
start,
|
|
48
|
+
embedOrigin,
|
|
49
|
+
signal
|
|
50
|
+
}) {
|
|
51
|
+
const url = `${mint.getAthenaApiBaseUrl(backendUrl)}${computerLiveDesktopSessionPath(assetId)}`;
|
|
52
|
+
const body = JSON.stringify({
|
|
53
|
+
start,
|
|
54
|
+
mode,
|
|
55
|
+
...embedOrigin ? { embed_origin: embedOrigin } : {}
|
|
56
|
+
});
|
|
57
|
+
const send = async (headers) => {
|
|
58
|
+
try {
|
|
59
|
+
return await fetch(url, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers: { ...headers, "Content-Type": "application/json" },
|
|
62
|
+
body,
|
|
63
|
+
signal: signal ?? null
|
|
64
|
+
});
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if ((error instanceof DOMException || error instanceof Error) && error.name === "AbortError") {
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
throw new errors.AthenaSdkError({
|
|
70
|
+
code: "live_desktop_failed",
|
|
71
|
+
message: "The computer’s desktop could not be reached.",
|
|
72
|
+
cause: error,
|
|
73
|
+
hint: "Check the network and that backendUrl points at the Agora API base."
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const response = await credentialRenewal.sendWithCredentialRenewal(auth, send);
|
|
78
|
+
const parsed = await response.json().catch(() => void 0);
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
throw new errors.AthenaSdkError({
|
|
81
|
+
code: response.status === 401 ? "auth_rejected" : "live_desktop_failed",
|
|
82
|
+
message: refusalDetail(parsed) ?? `Opening the computer’s desktop failed (${response.status}).`,
|
|
83
|
+
status: response.status,
|
|
84
|
+
detail: parsed,
|
|
85
|
+
hint: response.status === 403 ? "Watching a computer’s desktop needs edit access to it, in its own workspace." : void 0
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const session = readLiveDesktopSession(parsed);
|
|
89
|
+
if (!session) {
|
|
90
|
+
throw new errors.AthenaSdkError({
|
|
91
|
+
code: "live_desktop_failed",
|
|
92
|
+
message: "The computer’s desktop answered in a shape this app does not understand.",
|
|
93
|
+
status: response.status,
|
|
94
|
+
detail: parsed
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return session;
|
|
98
|
+
}
|
|
99
|
+
const LIVE_DESKTOP_VIEWER_MESSAGE_SOURCE = "athena-live-desktop";
|
|
100
|
+
function readLiveDesktopViewerMessage(data) {
|
|
101
|
+
if (!utils.isRecord(data) || data.source !== LIVE_DESKTOP_VIEWER_MESSAGE_SOURCE) return null;
|
|
102
|
+
switch (data.status) {
|
|
103
|
+
case "ready":
|
|
104
|
+
return { status: "ready" };
|
|
105
|
+
case "open":
|
|
106
|
+
return { status: "open" };
|
|
107
|
+
case "error":
|
|
108
|
+
return typeof data.detail === "string" ? { status: "error", detail: data.detail.slice(0, 300) } : null;
|
|
109
|
+
case "closed":
|
|
110
|
+
return typeof data.code === "number" ? {
|
|
111
|
+
status: "closed",
|
|
112
|
+
code: data.code,
|
|
113
|
+
reason: typeof data.reason === "string" ? data.reason.slice(0, 120) : ""
|
|
114
|
+
} : null;
|
|
115
|
+
default:
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function finalLiveDesktopCloseNotice(code) {
|
|
120
|
+
switch (code) {
|
|
121
|
+
case 4001:
|
|
122
|
+
return "The desktop session expired. Reconnect to start a new one.";
|
|
123
|
+
case 4003:
|
|
124
|
+
return "You no longer have access to this computer’s desktop.";
|
|
125
|
+
case 4008:
|
|
126
|
+
return "The desktop session reached its time limit, so the computer can go idle. Reconnect to keep watching.";
|
|
127
|
+
default:
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
exports.LIVE_DESKTOP_VIEWER_MESSAGE_SOURCE = LIVE_DESKTOP_VIEWER_MESSAGE_SOURCE;
|
|
132
|
+
exports.LiveDesktopMode = LiveDesktopMode;
|
|
133
|
+
exports.computerLiveDesktopSessionPath = computerLiveDesktopSessionPath;
|
|
134
|
+
exports.createComputerLiveDesktopSession = createComputerLiveDesktopSession;
|
|
135
|
+
exports.finalLiveDesktopCloseNotice = finalLiveDesktopCloseNotice;
|
|
136
|
+
exports.liveDesktopViewerUrl = liveDesktopViewerUrl;
|
|
137
|
+
exports.readLiveDesktopSession = readLiveDesktopSession;
|
|
138
|
+
exports.readLiveDesktopViewerMessage = readLiveDesktopViewerMessage;
|
|
139
|
+
//# sourceMappingURL=live-desktop-api.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-desktop-api.cjs","sources":["../../src/workbench/live-desktop-api.ts"],"sourcesContent":["/**\n * A computer's live desktop, as an app the computer serves on Athena embeds it.\n *\n * `POST /api/sandbox/computer/{asset_id}/live-desktop/session` (agora\n * `web/api/computer/live_desktop.py`) is the gate: VIEW with the workspace\n * fence, the computer in the caller's current workspace, EDIT and the\n * computer role policy, for watching as for control. It answers the viewer\n * page's path (carrying a 60 s single-use ticket), or why there is nothing to\n * show. The session names the page that will frame the viewer\n * (`embed_origin`, this page's own origin): agora accepts only an Athena app\n * the caller may open in the computer's workspace, and binds the viewer's\n * `frame-ancestors` and messages to it.\n *\n * The viewer page is Selkies' own web client, served by agora from the guest.\n * Whoever controls the computer controls that page, so its messages are hints\n * for the UI (connected, failed, closed) and never trusted beyond that.\n */\nimport { getAthenaApiBaseUrl } from '../collab/mint';\nimport { AthenaSdkError } from '../diagnostics/errors';\nimport { isRecord } from '../lib/utils';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport type { StatewireAuthSource } from '../runtime/statewire-auth-headers';\n\nexport const LiveDesktopMode = { WATCH: 'watch', CONTROL: 'control' } as const;\nexport type LiveDesktopMode = (typeof LiveDesktopMode)[keyof typeof LiveDesktopMode];\n\n/** What the session route answered. */\nexport type LiveDesktopSession =\n | {\n status: 'ready';\n /** A path on agora's own origin, carrying the single-use ticket. */\n viewerPath: string;\n mode: LiveDesktopMode;\n /** The remote screen, when known: what the frame keeps the aspect of. */\n display: { width: number; height: number } | null;\n }\n | { status: 'display_not_running'; detail: string }\n | { status: 'unsupported'; detail: string };\n\nexport function computerLiveDesktopSessionPath(assetId: string): string {\n return `/api/sandbox/computer/${encodeURIComponent(assetId)}/live-desktop/session`;\n}\n\nfunction readDisplay(value: unknown): { width: number; height: number } | null {\n if (!isRecord(value)) return null;\n const { width, height } = value;\n return typeof width === 'number' && width > 0 && typeof height === 'number' && height > 0\n ? { width, height }\n : null;\n}\n\n/** The route's body as a session, or `null` for a shape the pane cannot use. */\nexport function readLiveDesktopSession(body: unknown): LiveDesktopSession | null {\n if (!isRecord(body)) return null;\n const detail = typeof body.detail === 'string' ? body.detail : '';\n if (body.status === 'display_not_running') return { status: 'display_not_running', detail };\n if (body.status === 'unsupported') return { status: 'unsupported', detail };\n if (body.status !== 'ready') return null;\n const { viewer_path: viewerPath, mode } = body;\n // Only a path on agora's own origin, never a protocol-relative URL.\n if (typeof viewerPath !== 'string' || !/^\\/(?!\\/)/.test(viewerPath)) return null;\n if (mode !== LiveDesktopMode.WATCH && mode !== LiveDesktopMode.CONTROL) return null;\n return { status: 'ready', viewerPath, mode, display: readDisplay(body.display) };\n}\n\n/**\n * The viewer page's URL on agora's origin. Throws when *viewerPath* would\n * leave that origin: the frame is given agora's page or nothing.\n */\nexport function liveDesktopViewerUrl({\n backendUrl,\n viewerPath,\n}: {\n backendUrl: string;\n viewerPath: string;\n}): string {\n const base = new URL(`${getAthenaApiBaseUrl(backendUrl)}/`);\n const url = new URL(viewerPath, base);\n if (url.origin !== base.origin) {\n throw new Error('The live desktop viewer must be served by the Athena API');\n }\n return url.toString();\n}\n\nfunction refusalDetail(body: unknown): string | null {\n if (!isRecord(body)) return null;\n return typeof body.detail === 'string' && body.detail ? body.detail : null;\n}\n\n/** Ask for a viewer of the computer's desktop. Every failure is an `AthenaSdkError`. */\nexport async function createComputerLiveDesktopSession({\n backendUrl,\n auth,\n assetId,\n mode,\n start,\n embedOrigin,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n assetId: string;\n mode: LiveDesktopMode;\n /** Start the computer's display when it is down (this also opens its browser). */\n start: boolean;\n /** The origin of the page that will frame the viewer (this page's). */\n embedOrigin: string | null;\n signal?: AbortSignal;\n}): Promise<LiveDesktopSession> {\n const url = `${getAthenaApiBaseUrl(backendUrl)}${computerLiveDesktopSessionPath(assetId)}`;\n const body = JSON.stringify({\n start,\n mode,\n ...(embedOrigin ? { embed_origin: embedOrigin } : {}),\n });\n const send = async (headers: Record<string, string>): Promise<Response> => {\n try {\n return await fetch(url, {\n method: 'POST',\n headers: { ...headers, 'Content-Type': 'application/json' },\n body,\n signal: signal ?? null,\n });\n } catch (error) {\n if ((error instanceof DOMException || error instanceof Error) && error.name === 'AbortError') {\n throw error;\n }\n throw new AthenaSdkError({\n code: 'live_desktop_failed',\n message: 'The computer’s desktop could not be reached.',\n cause: error,\n hint: 'Check the network and that backendUrl points at the Agora API base.',\n });\n }\n };\n const response = await sendWithCredentialRenewal(auth, send);\n const parsed: unknown = await response.json().catch(() => undefined);\n if (!response.ok) {\n throw new AthenaSdkError({\n code: response.status === 401 ? 'auth_rejected' : 'live_desktop_failed',\n message:\n refusalDetail(parsed) ?? `Opening the computer’s desktop failed (${response.status}).`,\n status: response.status,\n detail: parsed,\n hint:\n response.status === 403\n ? 'Watching a computer’s desktop needs edit access to it, in its own workspace.'\n : undefined,\n });\n }\n const session = readLiveDesktopSession(parsed);\n if (!session) {\n throw new AthenaSdkError({\n code: 'live_desktop_failed',\n message: 'The computer’s desktop answered in a shape this app does not understand.',\n status: response.status,\n detail: parsed,\n });\n }\n return session;\n}\n\n/** Names the viewer page's messages (`LIVE_DESKTOP_VIEWER_MESSAGE_SOURCE` in agora). */\nexport const LIVE_DESKTOP_VIEWER_MESSAGE_SOURCE = 'athena-live-desktop';\n\n/** The viewer page's messages the pane acts on; the rest are ignored. */\nexport type LiveDesktopViewerMessage =\n | { status: 'ready' }\n | { status: 'error'; detail: string }\n | { status: 'open' }\n | { status: 'closed'; code: number; reason: string };\n\nexport function readLiveDesktopViewerMessage(data: unknown): LiveDesktopViewerMessage | null {\n if (!isRecord(data) || data.source !== LIVE_DESKTOP_VIEWER_MESSAGE_SOURCE) return null;\n switch (data.status) {\n case 'ready':\n return { status: 'ready' };\n case 'open':\n return { status: 'open' };\n case 'error':\n return typeof data.detail === 'string'\n ? { status: 'error', detail: data.detail.slice(0, 300) }\n : null;\n case 'closed':\n return typeof data.code === 'number'\n ? {\n status: 'closed',\n code: data.code,\n reason: typeof data.reason === 'string' ? data.reason.slice(0, 120) : '',\n }\n : null;\n default:\n return null;\n }\n}\n\n/**\n * Close codes agora's relay ends a view with for good\n * (`LIVE_DESKTOP_CLOSE_*` in agora `live_desktop.py`): the view is over and\n * the pane says why. Any other close is a drop Selkies re-dials by itself.\n */\nexport function finalLiveDesktopCloseNotice(code: number): string | null {\n switch (code) {\n case 4001:\n return 'The desktop session expired. Reconnect to start a new one.';\n case 4003:\n return 'You no longer have access to this computer’s desktop.';\n case 4008:\n return 'The desktop session reached its time limit, so the computer can go idle. Reconnect to keep watching.';\n default:\n return null;\n }\n}\n"],"names":["isRecord","getAthenaApiBaseUrl","AthenaSdkError","sendWithCredentialRenewal"],"mappings":"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"}
|