@av-pi-studio/web-client 0.0.40 → 0.0.41

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.
Files changed (29) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/web/assets/{BinaryFallbackViewer-CJ1-OtwU.js → BinaryFallbackViewer-Cb-P_8V-.js} +2 -2
  3. package/dist/web/assets/{BinaryFallbackViewer-CJ1-OtwU.js.map → BinaryFallbackViewer-Cb-P_8V-.js.map} +1 -1
  4. package/dist/web/assets/{ChatPanel-BORrt6x0.js → ChatPanel-D_3cLBoK.js} +2 -2
  5. package/dist/web/assets/ChatPanel-D_3cLBoK.js.map +1 -0
  6. package/dist/web/assets/{CodeView-Cu2yyMOv.js → CodeView-B1cJEve4.js} +2 -2
  7. package/dist/web/assets/{CodeView-Cu2yyMOv.js.map → CodeView-B1cJEve4.js.map} +1 -1
  8. package/dist/web/assets/{FilePanel-CMzv0HtU.js → FilePanel-YoyZJ2v2.js} +3 -3
  9. package/dist/web/assets/{FilePanel-CMzv0HtU.js.map → FilePanel-YoyZJ2v2.js.map} +1 -1
  10. package/dist/web/assets/{ImageViewer-Cz4spWgk.js → ImageViewer-BvKE7WrT.js} +2 -2
  11. package/dist/web/assets/{ImageViewer-Cz4spWgk.js.map → ImageViewer-BvKE7WrT.js.map} +1 -1
  12. package/dist/web/assets/{MarkdownFileViewer-BJx06RrQ.js → MarkdownFileViewer-Ct7fWGmR.js} +2 -2
  13. package/dist/web/assets/{MarkdownFileViewer-BJx06RrQ.js.map → MarkdownFileViewer-Ct7fWGmR.js.map} +1 -1
  14. package/dist/web/assets/{TerminalPanel-Djo6xzYk.js → TerminalPanel-rKZq5rWo.js} +2 -2
  15. package/dist/web/assets/{TerminalPanel-Djo6xzYk.js.map → TerminalPanel-rKZq5rWo.js.map} +1 -1
  16. package/dist/web/assets/{TextViewer-DFEviuBM.js → TextViewer-BsdUzTN-.js} +2 -2
  17. package/dist/web/assets/{TextViewer-DFEviuBM.js.map → TextViewer-BsdUzTN-.js.map} +1 -1
  18. package/dist/web/assets/{VideoViewer-Db4EXUHf.js → VideoViewer-BoA5BwxX.js} +2 -2
  19. package/dist/web/assets/{VideoViewer-Db4EXUHf.js.map → VideoViewer-BoA5BwxX.js.map} +1 -1
  20. package/dist/web/assets/{index-Blzywf8O.js → index-BTP_UxAo.js} +3 -3
  21. package/dist/web/assets/index-BTP_UxAo.js.map +1 -0
  22. package/dist/web/assets/index-EefWgf31.css +1 -0
  23. package/dist/web/assets/{use-file-download-BBVfhuPL.js → use-file-download-CwyNTuhL.js} +2 -2
  24. package/dist/web/assets/{use-file-download-BBVfhuPL.js.map → use-file-download-CwyNTuhL.js.map} +1 -1
  25. package/dist/web/index.html +2 -2
  26. package/package.json +3 -3
  27. package/dist/web/assets/ChatPanel-BORrt6x0.js.map +0 -1
  28. package/dist/web/assets/index-Blzywf8O.js.map +0 -1
  29. package/dist/web/assets/index-C_fVQpEW.css +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"mappings":";gvDAIO,MAAMA,GAAc,IAAIC,GCCxB,SAASC,GAASC,EAAkB,CACzC,MAAMC,EAAID,EAAI,OAAO,QAAQ,KAAM,EAAE,EAC/BE,EACJD,EAAE,SAAW,EACTA,EACG,MAAM,EAAE,EACR,IAAKE,GAAMA,EAAIA,CAAC,EAChB,KAAK,EAAE,EACVF,EACN,GAAIC,EAAK,SAAW,GAAK,eAAe,KAAKA,CAAI,EAC/C,MAAM,IAAI,MAAM,sBAAsBF,CAAG,EAAE,EAE7C,MAAO,CACL,EAAG,SAASE,EAAK,MAAM,EAAG,CAAC,EAAG,EAAE,EAChC,EAAG,SAASA,EAAK,MAAM,EAAG,CAAC,EAAG,EAAE,EAChC,EAAG,SAASA,EAAK,MAAM,EAAG,CAAC,EAAG,EAAE,EAEpC,CAEA,SAASE,GAASC,EAAmB,CACnC,OAAO,KAAK,IAAI,EAAG,KAAK,IAAI,IAAK,KAAK,MAAMA,CAAC,CAAC,CAAC,CACjD,CAEO,SAASC,GAAS,CAAE,EAAAC,EAAG,EAAAC,EAAG,EAAAC,GAAkB,CACjD,MAAMC,EAAML,GAAcD,GAASC,CAAC,EAAE,SAAS,EAAE,EAAE,SAAS,EAAG,GAAG,EAClE,MAAO,IAAIK,EAAGH,CAAC,CAAC,GAAGG,EAAGF,CAAC,CAAC,GAAGE,EAAGD,CAAC,CAAC,EAClC,CAGO,SAASE,GAAkBX,EAAqB,CACrD,KAAM,CAAE,EAAAO,EAAG,EAAAC,EAAG,EAAAC,CAAA,EAAMV,GAASC,CAAG,EAC1BY,EAAWC,GAAc,CAC7B,MAAMC,EAAID,EAAI,IACd,OAAOC,GAAK,OAAUA,EAAI,QAAUA,EAAI,MAAS,QAAU,GAC7D,EACA,MAAO,OAASF,EAAQL,CAAC,EAAI,MAASK,EAAQJ,CAAC,EAAI,MAASI,EAAQH,CAAC,CACvE,CAGO,SAASM,GAAmBf,EAAagB,EAAO,UAAWC,EAAQ,UAAmB,CAC3F,OAAON,GAAkBX,CAAG,EAAI,IAAOgB,EAAOC,CAChD,CAGO,SAASC,GAAQlB,EAAamB,EAAwB,CAC3D,KAAM,CAAE,EAAAZ,EAAG,EAAAC,EAAG,EAAAC,CAAA,EAAMV,GAASC,CAAG,EAC1BoB,EAAI,KAAK,IAAI,EAAG,KAAK,IAAI,EAAGD,CAAM,CAAC,EACzC,OAAOb,GAAS,CACd,EAAGC,GAAK,IAAMA,GAAKa,EACnB,EAAGZ,GAAK,IAAMA,GAAKY,EACnB,EAAGX,GAAK,IAAMA,GAAKW,CAAA,CACpB,CACH,CASO,SAASC,GAAWC,EAAwB,CACjD,MAAMrB,EAAIqB,EAAM,OAAO,QAAQ,KAAM,EAAE,EACvC,OAAQrB,EAAE,SAAW,GAAKA,EAAE,SAAW,IAAM,CAAC,eAAe,KAAKA,CAAC,CACrE,CClDA,MAAMsB,EAAQ,CACZC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,KACkB,CAClB,GAAMV,EACN,IAAOC,EACP,IAAOC,EACP,IAAOC,EACP,IAAOC,EACP,IAAOC,EACP,IAAOC,EACP,IAAOC,EACP,IAAOC,EACP,IAAOC,EACP,IAAOC,CACT,GAEaC,EAAU,CACrB,KAAMZ,EACJ,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,WAEF,KAAMA,EACJ,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,WAEF,MAAOA,EACL,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,WAEF,KAAMA,EACJ,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,WAEF,MAAOA,EACL,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,WAEF,IAAKA,EACH,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,WAEF,KAAMA,EACJ,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,WAEF,MAAOA,EACL,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,WAEF,OAAQA,EACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,WAEF,OAAQA,EACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,WAEF,OAAQA,EACN,U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","names":["queryClient","QueryClient","hexToRgb","hex","h","full","c","clamp255","n","rgbToHex","r","g","b","to","relativeLuminance","channel","v","s","contrastForeground","dark","light","lighten","amount","a","isHexColor","value","scale","s50","s100","s200","s300","s400","s500","s600","s700","s800","s900","s950","palette","DARK_STATUS","LIGHT_STATUS","darkSyntax","lightSyntax","terminalFrom","surface0","foreground","accent","buildDarkColors","tint","accentForeground","buildLightColors","THEME_SWATCHES","THEME_NAMES","buildVariantColors","name","_variants","colorScheme","THEME_VARIANTS","spacing","baseFontSize","fontWeight","borderRadius","borderWidth","opacity","iconSize","baseLineHeight","DEFAULT_UI_FONT","DEFAULT_MONO_FONT","buildShadows","color","buildTheme","variant","FONT_SIZE_BASE","FONT_SIZE_MIN","FONT_SIZE_MAX","applyAppearance","base","settings","themeName","baseTheme","uiFont","monoFont","fontSize","key","k","getTheme","DEFAULT_THEME_NAME","flattenThemeToVars","theme","vars","flattenColors","val","colors","_palette","_syntax","_terminal","semanticColors","flattenSyntax","syntax","camelToKebab","str","m","applyVarsToRoot","style","prop","scheme","applyThemeToDOM","AssetRefSchema","z.string","BrandColorsSchema","z.object","BrandAssetsSchema","BrandLinksSchema","BrandLegalSchema","resolveAccentColors","accentBright","swatch","injectBrandAccent","resolved","STORAGE_KEY","createAppearanceController","store","brandConfig","state","loadInitial","mediaCleanup","resolveThemeName","mode","buildResolved","persist","serialized","raw","parsed","resolvedTheme","update","controller","patch","mq","handler","ThemeBoundary","children","controllerRef","useRef","useEffect","localKvStore","_a","AppProviders","jsx","QueryClientProvider","BUTTON_MIN_HEIGHT","BUTTON_ICON_SIZE","BUTTON_PADDING_H","BUTTON_FONT_SIZE","resolveButtonState","opts","disabled","loading","Button","size","leftIcon","trailing","iconOnly","className","rest","isDisabled","iconPx","jsxs","clsx","styles","Loader2","TextInput","forwardRef","ref","TextArea","isoTimestampSchema","wireTimestampSchema","z.union","z.number","clientTypeSchema","z.enum","helloSchema","z.literal","z.record","z.boolean","serverInfoPayloadSchema","z.unknown","statusSchema","pingSchema","pongSchema","agentStatusEnum","imageAttachmentSchema","agentAttachmentsSchema","z.array","agentSessionConfigSchema","createAgentRequestSchema","createAgentResponseSchema","agentUpdateSchema","agentStatusMessageSchema","agentListSchema","agentDeletedSchema","agentArchivedSchema","toolCallDetailSchema","z.discriminatedUnion","agentStreamEventSchema","agentStreamSchema","timelineDirectionEnum","sourceSeqRangeSchema","fetchAgentTimelineRequestSchema","fetchAgentTimelineResponseSchema","agentPermissionRequestSchema","agentPermissionResolvedSchema","respondToPermissionRequestSchema","respondToPermissionResponseSchema","legacyRespondToPermissionSchema","rewindModeSchema","agentRewindRequestSchema","agentRewindResponseSchema","agentTokenUsageSchema","agentContextUsageSchema","agentSessionStatsRequestSchema","agentSessionStatsResponseSchema","agentCompactRequestSchema","agentCompactResponseSchema","agentNewSessionRequestSchema","agentNewSessionResponseSchema","agentSwitchSessionRequestSchema","agentSwitchSessionResponseSchema","agentForkRequestSchema","agentForkResponseSchema","agentForkMessagesRequestSchema","agentForkMessagesResponseSchema","agentCloneRequestSchema","agentCloneResponseSchema","agentSetSessionNameRequestSchema","agentSetSessionNameResponseSchema","agentExportHtmlRequestSchema","agentExportHtmlResponseSchema","agentSetModelRequestSchema","agentSetModelResponseSchema","agentCycleModelRequestSchema","agentCycleModelResponseSchema","agentLastAssistantTextRequestSchema","agentLastAssistantTextResponseSchema","steerAgentRequestSchema","steerAgentResponseSchema","followUpAgentRequestSchema","followUpAgentResponseSchema","rpcErrorSchema","sessionMessageSchema","sessionMessageBaseSchema","sessionEnvelopeSchema","TerminalOpcode","OPCODE_BY_VALUE","terminalResizeSchema","TerminalFrameError","message","textEncoder","textDecoder","assertSlot","slot","frameWithPayload","opcodeValue","payload","out","encodeTerminalFrame","frame","json","decodeTerminalFrame","bytes","opcode","result","FileTransferOpcode","fileTransferBeginSchema","FileTransferFrameError","assertStream","stream","encodeFileTransferFrame","f","decodeFileTransferFrame","tryDecodeFileTransferFrame","providerIdSchema","colorTierSchema","agentCapabilityFlagsSchema","providerModeSchema","reasonString","reason","createWebSocketTransport","factory","ws","self","url","resolve","reject","create","err","ev","data","_b","_c","_d","code","CHARS","encodeBase64","i","b0","hasB1","hasB2","b1","b2","decodeBase64","input","clean","buffer","bits","ch","parseFrame","NONCE_LENGTH","sealBytes","sharedKey","nonce","nacl","ciphertext","combined","openBytes","seal","plaintext","open","opened","wireAppMessages","role","attachment","transport","events","getSharedKey","isReady","closed","createClientChannel","daemonPublicKey","ephemeral","ready","plumbing","SESSION_ID_BYTES","deriveRelaySessionId","publicKey","digest","relayDialUrl","relay","host","createRelayTransport","sessionId","socket","messageHandlers","closeHandlers","parsePairingUrl","hashIndex","fragment","params","publicKeyB64","relayEndpoint","PROTOCOL_VERSION","randomId","RpcError","requestId","RpcTimeoutError","DaemonClient","options","__publicField","flag","handshake","info","type","timeoutMs","timeout","timer","next","_code","pending","buf","opcodeByte","envelope","msg","PiStudioClient","daemon","req","agentId","AgentHandle","workspaceId","WorkspaceHandle","ProviderHandle","TimelineHandle","prompt","customInstructions","sessionPath","entryId","outputPath","provider","modelId","cwd","UPLOAD_CHUNK_BYTES","concat","chunks","total","sum","offset","chunk","FileTransferClient","path","tokenResponse","requestResponse","transferId","response","ReconnectionManager","cb","ms","attempt","jitterAmount","delay","error","normalizeDaemonUrl","schemeMatch","resolveConnectTarget","offer","generateClientId","useConnectionStore","set","get","target","u","client","reconnection","MIN_SIDEBAR_WIDTH","MAX_SIDEBAR_WIDTH","DEFAULT_LEFT_SIDEBAR_WIDTH","DEFAULT_RIGHT_SIDEBAR_WIDTH","clampSidebarWidth","width","useUiStore","password","x","y","isDirectory","tab","deltaX","LABEL","ConnectionStatus","status","Toolbar","connect","disconnect","setHost","setPassword","leftSidebarCollapsed","toggleLeftSidebar","rightSidebarCollapsed","toggleRightSidebar","connected","e","PanelLeftOpen","PanelLeftClose","PanelRightOpen","PanelRightClose","EMPTY_TIMELINE","rowSeq","nextRowId","toolDetailText","tool","toolCallKey","callId","onTurnStarted","onAssistantMessage","text","rows","index","row","onReasoning","mergeTool","prev","merged","onToolCall","event","existingIndex","onTurnCompleted","onTurnFailed","finalized","onTurnCanceled","onError","onUserMessage","images","messageId","idx","onQueueUpdate","steering","changed","applyStreamEvent","addOptimisticUserMessage","clientMessageId","queued","markUserMessageFailed","sessionSeq","newSessionId","useSessionStore","id","entry","model","modelProvider","title","timeline","applyStreamEventToTimeline","lastAssistant","addOptimisticUserMessageToTimeline","markUserMessageFailedInTimeline","_removed","order","activeSessionId","defaultModelCache","resolveDefaultModel","cached","promise","res","materializing","ensureMaterialized","inFlight","current","fresh","config","discardIfEmpty","tabsInWorkspace","tabs","t","syncActiveSession","useTabStore","existing","fallbackTab","activeTabId","lastActiveTabByWorkspace","siblings","siblingIdx","stillOpen","activatedTab","switchedTab","remembered","fromId","toId","fromIdx","toIdx","moved","label","tabIds","staged","slotOrToken","terminalCount","openNewTerminal","workspaceCwd","openNewChat","closeTab","tabId","useExplorerStore","expandedByRoot","expanded","parentPath","kind","cachedHomeDir","homeDirListeners","onHomeDirResolved","listener","resolveHome","first","peekCachedHomeDir","resolveTildePath","useHomeDir","homeDir","setHomeDir","useState","statusDotColor","requiresAttention","attentionReason","showInactive","STATUS_DOT_SIZE","COLOR_TOKEN_VAR","StatusDot","colorToken","spinning","MAP","toDotStatus","SessionItem","session","active","onSelect","onOpenMenu","rect","MoreVertical","SessionContextMenu","menu","closeSessionMenu","triggerRef","setOpen","canStop","handleOpenChange","rename","stop","removeLocal","archive","remove","DropdownMenu.Root","DropdownMenu.Trigger","DropdownMenu.Portal","DropdownMenu.Content","DropdownMenu.Item","DropdownMenu.Separator","WorkspaceGroupHeader","sessionCount","collapsed","onToggleCollapsed","onNewSession","ChevronRight","ChevronDown","FolderClosed","Plus","normalizeCwd","groupSessionsByWorkspace","sessions","groups","bucket","sessionsInGroup","workspaceLabel","SessionList","activate","openTab","openSessionMenu","setCwd","openCwdPicker","collapsedWorkspaces","toggleWorkspaceCollapsed","handleNewSession","targetWorkspaceCwd","targetCwd","handleSelect","FolderOpen","group","ICON_BY_KIND","MessageSquare","FileText","GitCompare","TerminalSquare","TabItem","Icon","attributes","listeners","setNodeRef","transform","transition","isDragging","useSortable","CSS","X","NewTabMenu","TabStrip","activeWorkspaceCwd","reorder","visibleTabs","sensors","useSensors","useSensor","PointerSensor","handleDragEnd","over","DndContext","closestCenter","SortableContext","horizontalListSortingStrategy","SPINNER_SIZE_PX","SPINNER_BORDER_PX","Spinner","ariaLabel","px","borderPx","ChatPanel","lazy","__vitePreload","FilePanel","TerminalPanel","PANEL_BY_KIND","TabPanelHost","hasTabsInWorkspace","Panel","Suspense","rpcKeys","normalizeEntry","sortEntries","entries","fetchExplorerListing","useExplorer","enabled","useQuery","useExplorerTree","paths","useMemo","results","useQueries","map","transferByDaemon","transferFor","transfer","saveBlob","fileName","mimeType","blob","anchor","useFileTransfer","useQueryClient","upload","useCallback","dir","file","saveToDisk","joinPath","pushChildren","dirPath","depth","tree","draft","node","pushEntry","isExpanded","flattenTree","rootPath","TreeDraftRow","draftKind","indentStyle","onSubmit","onCancel","setName","Folder","FileIcon","trimmed","INDENT_PX","TreeNode","onToggle","onOpenFile","onContextMenu","onSubmitDraft","onCancelDraft","indent","FileContextMenu","closeFileMenu","startDraft","download","startNew","parent","Fragment","ERROR_MESSAGES","createEntry","ROW_HEIGHT_PX","FileExplorer","setRoot","toggle","cancelDraft","openFileMenu","setStatus","dragging","setDragging","dropTargetDir","setDropTargetDir","fileInputRef","scrollRef","lastSeededCwd","virtualizer","useVirtualizer","handleOpenFile","submitDraft","created","uploadFiles","files","existingListing","clashes","names","handleDrop","handleDragOver","FilePlus","FolderPlus","Upload","virtualRow","EMPTY_BRANCH_META","useGitStore","projection","changes","BADGE_LABEL","ChangesPanel","handleClick","change","RightSidebar","rightSidebarTab","setRightSidebarTab","handleRefresh","RotateCw","useStatsStore","partial","POLL_INTERVAL_MS","shouldRepollOnStatusChange","applySessionStats","_e","_f","useSessionStats","sessionStatus","inFlightRef","prevStatusRef","cancelled","poll","interval","prevStatus","isCheckoutStatusUpdate","useCheckoutStatus","applyProjection","setSubscribedCwd","clientRef","unsubscribeMessages","PLACEHOLDER","formatTokens","formatPercent","p","pct","formatCost","dp","formatCwd","home","formatBranchMeta","ahead","behind","parts","useProviderModels","filterOptions","query","q","o","sortCurrentFirst","models","currentModel","dedupeById","seen","ModelMenu","renderTrigger","setQuery","searchRef","isLoading","isError","sortedModels","providerById","filtered","errorMessage","handleSearchKeyDown","opt","Check","StatusBar","setModel","gitAvailable","branch","detached","conflictCount","dirtyCount","stats","segments","cwdText","branchMeta","GitBranch","contextText","Gauge","Coins","DollarSign","handleSelectModel","Cpu","seg","checked","onChange","placeholder","DialogClose","RadixDialog.Close","Dialog","onOpenChange","footer","bare","RadixDialog.Root","RadixDialog.Portal","RadixDialog.Overlay","RadixDialog.Content","RadixDialog.Title","ResizeHandle","side","onResize","startXRef","handlePointerMove","handlePointerUp","handlePointerDown","openWorkspace","OpenWorkspaceDialog","closeDialog","uiCwd","setPathState","inputValue","setInputValue","creating","setCreating","newName","setNewName","createError","setCreateError","createBusy","setCreateBusy","seed","resetCreate","dirEntries","navigate","handleUp","handleGo","handleCreate","handleOpen","ArrowUp","WorkspacePage","leftSidebarWidth","rightSidebarWidth","resizeLeftSidebar","resizeRightSidebar","useConnectionBoot","ranRef","pairingUrl","qs","pair","shouldConnect","ui","pw","isTimelineItem","flattenTimelineItems","items","hasStringModel","useSessionRestore","restoredRef","agents","sessionStore","agent","page","isEmptyDraft","useTerminalRestore","terminals","useShortcuts","onKeyDown","mod","Boot","App","root","createRoot","StrictMode"],"ignoreList":[],"sources":["../../../src/lib/connection/query-client.ts","../../../src/theme/color-utils.ts","../../../src/theme/palette.ts","../../../src/theme/colors.ts","../../../src/theme/variants.ts","../../../src/theme/tokens.ts","../../../src/theme/theme.ts","../../../src/theme/css-bridge.ts","../../../src/brand/config.ts","../../../src/brand/theme-injection.ts","../../../src/theme/appearance-store.ts","../../../src/theme/ThemeBoundary.tsx","../../../src/providers/kv-store.ts","../../../src/providers/AppProviders.tsx","../../../src/ui/button.ts","../../../src/components/primitives/Button.tsx","../../../src/components/primitives/TextInput.tsx","../../../../protocol/src/validation.ts","../../../../protocol/src/messages.ts","../../../../protocol/src/binary-frames/terminal-stream-protocol.ts","../../../../protocol/src/binary-frames/file-transfer-protocol.ts","../../../../protocol/src/provider-manifest.ts","../../../../client/src/transport.ts","../../../../relay/dist/base64.js","../../../../relay/dist/channel.js","../../../../relay/dist/session-id.js","../../../../client/src/relay-transport.ts","../../../../client/src/pairing.ts","../../../../client/src/daemon-client.ts","../../../../client/src/pistudio-client.ts","../../../../client/src/file-transfer-client.ts","../../../../client/src/reconnect.ts","../../../src/lib/connection/normalize-url.ts","../../../src/lib/connection/resolve-connect-target.ts","../../../src/lib/connection/connection-store.ts","../../../src/stores/ui-store.ts","../../../src/features/connection/ConnectionStatus.tsx","../../../src/features/connection/Toolbar.tsx","../../../src/timeline/row-model.ts","../../../src/timeline/reducer.ts","../../../src/stores/session-store.ts","../../../src/stores/materialize.ts","../../../src/stores/tab-store.ts","../../../src/stores/explorer-store.ts","../../../src/hooks/use-home-dir.ts","../../../src/ui/status-dot.ts","../../../src/components/primitives/StatusDot.tsx","../../../src/features/sessions/status-map.ts","../../../src/features/sessions/SessionItem.tsx","../../../src/features/sessions/SessionContextMenu.tsx","../../../src/features/sessions/WorkspaceGroupHeader.tsx","../../../src/features/sessions/workspace-grouping.ts","../../../src/features/sessions/SessionList.tsx","../../../src/features/workspace/TabStrip.tsx","../../../src/components/primitives/Spinner.tsx","../../../src/features/workspace/panel-registry.ts","../../../src/features/workspace/TabPanelHost.tsx","../../../src/lib/connection/rpc-keys.ts","../../../src/hooks/use-explorer.ts","../../../src/hooks/use-explorer-tree.ts","../../../src/hooks/file-transfer-instance.ts","../../../src/hooks/use-file-transfer.ts","../../../src/features/files/file-tree.ts","../../../src/features/files/TreeDraftRow.tsx","../../../src/features/files/TreeNode.tsx","../../../src/features/files/FileContextMenu.tsx","../../../src/features/files/create-entry.ts","../../../src/features/files/FileExplorer.tsx","../../../src/stores/git-store.ts","../../../src/features/git/ChangesPanel.tsx","../../../src/features/files/RightSidebar.tsx","../../../src/stores/stats-store.ts","../../../src/hooks/use-session-stats.ts","../../../src/hooks/use-checkout-status.ts","../../../src/features/workspace/status-bar-format.ts","../../../src/hooks/use-provider-models.ts","../../../src/ui/combobox.ts","../../../src/features/chat/model-menu-sort.ts","../../../src/features/chat/ModelMenu.tsx","../../../src/features/workspace/StatusBar.tsx","../../../src/components/primitives/Checkbox.tsx","../../../src/components/primitives/Select.tsx","../../../src/components/primitives/Dialog.tsx","../../../src/components/primitives/ResizeHandle.tsx","../../../src/features/sessions/open-workspace.ts","../../../src/features/workspace-picker/OpenWorkspaceDialog.tsx","../../../src/routes/WorkspacePage.tsx","../../../src/hooks/use-connection.ts","../../../src/lib/protocol/events.ts","../../../src/hooks/use-session-restore.ts","../../../src/hooks/use-terminal-restore.ts","../../../src/hooks/use-shortcuts.ts","../../../src/app.tsx","../../../src/main.tsx"],"sourcesContent":["import { QueryClient } from \"@tanstack/react-query\";\n\n// Shared TanStack Query cache. Tuning (staleTime, retry, invalidation on the\n// `filesChanged` signal) is refined as data-fetching hooks land.\nexport const queryClient = new QueryClient();\n","// Small pure color helpers used by the theme builder and (later) brand accent derivation.\n// No external deps; operates on `#rrggbb` / `#rgb` hex strings.\n\nexport type Rgb = { r: number; g: number; b: number };\n\nexport function hexToRgb(hex: string): Rgb {\n const h = hex.trim().replace(/^#/, \"\");\n const full =\n h.length === 3\n ? h\n .split(\"\")\n .map((c) => c + c)\n .join(\"\")\n : h;\n if (full.length !== 6 || /[^0-9a-fA-F]/.test(full)) {\n throw new Error(`Invalid hex color: ${hex}`);\n }\n return {\n r: parseInt(full.slice(0, 2), 16),\n g: parseInt(full.slice(2, 4), 16),\n b: parseInt(full.slice(4, 6), 16),\n };\n}\n\nfunction clamp255(n: number): number {\n return Math.max(0, Math.min(255, Math.round(n)));\n}\n\nexport function rgbToHex({ r, g, b }: Rgb): string {\n const to = (n: number) => clamp255(n).toString(16).padStart(2, \"0\");\n return `#${to(r)}${to(g)}${to(b)}`;\n}\n\n// Relative luminance per WCAG (0 = black, 1 = white).\nexport function relativeLuminance(hex: string): number {\n const { r, g, b } = hexToRgb(hex);\n const channel = (v: number) => {\n const s = v / 255;\n return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);\n}\n\n// Contrast-safe text color to place ON TOP of `hex`.\nexport function contrastForeground(hex: string, dark = \"#18181b\", light = \"#ffffff\"): string {\n return relativeLuminance(hex) > 0.45 ? dark : light;\n}\n\n// Mix `hex` toward white by `amount` (0..1). Used to derive an accentBright when omitted.\nexport function lighten(hex: string, amount: number): string {\n const { r, g, b } = hexToRgb(hex);\n const a = Math.max(0, Math.min(1, amount));\n return rgbToHex({\n r: r + (255 - r) * a,\n g: g + (255 - g) * a,\n b: b + (255 - b) * a,\n });\n}\n\n// Mix `hex` toward black by `amount` (0..1).\nexport function darken(hex: string, amount: number): string {\n const { r, g, b } = hexToRgb(hex);\n const a = Math.max(0, Math.min(1, amount));\n return rgbToHex({ r: r * (1 - a), g: g * (1 - a), b: b * (1 - a) });\n}\n\nexport function isHexColor(value: string): boolean {\n const h = value.trim().replace(/^#/, \"\");\n return (h.length === 3 || h.length === 6) && !/[^0-9a-fA-F]/.test(h);\n}\n","// Design-system raw color palette.\n// Tailwind-style numeric scales referenced by design-system.md § Colors (`colors.palette`).\n// Components reach into these only for FIXED signal colors that must not shift per theme.\n\nexport type PaletteShade =\n | \"50\"\n | \"100\"\n | \"200\"\n | \"300\"\n | \"400\"\n | \"500\"\n | \"600\"\n | \"700\"\n | \"800\"\n | \"900\"\n | \"950\";\n\nexport type PaletteScale = Readonly<Record<PaletteShade, string>>;\n\nconst scale = (\n s50: string,\n s100: string,\n s200: string,\n s300: string,\n s400: string,\n s500: string,\n s600: string,\n s700: string,\n s800: string,\n s900: string,\n s950: string,\n): PaletteScale => ({\n \"50\": s50,\n \"100\": s100,\n \"200\": s200,\n \"300\": s300,\n \"400\": s400,\n \"500\": s500,\n \"600\": s600,\n \"700\": s700,\n \"800\": s800,\n \"900\": s900,\n \"950\": s950,\n});\n\nexport const palette = {\n zinc: scale(\n \"#fafafa\",\n \"#f4f4f5\",\n \"#e4e4e7\",\n \"#d4d4d8\",\n \"#a1a1aa\",\n \"#71717a\",\n \"#52525b\",\n \"#3f3f46\",\n \"#27272a\",\n \"#18181b\",\n \"#09090b\",\n ),\n gray: scale(\n \"#f9fafb\",\n \"#f3f4f6\",\n \"#e5e7eb\",\n \"#d1d5db\",\n \"#9ca3af\",\n \"#6b7280\",\n \"#4b5563\",\n \"#374151\",\n \"#1f2937\",\n \"#111827\",\n \"#030712\",\n ),\n slate: scale(\n \"#f8fafc\",\n \"#f1f5f9\",\n \"#e2e8f0\",\n \"#cbd5e1\",\n \"#94a3b8\",\n \"#64748b\",\n \"#475569\",\n \"#334155\",\n \"#1e293b\",\n \"#0f172a\",\n \"#020617\",\n ),\n blue: scale(\n \"#eff6ff\",\n \"#dbeafe\",\n \"#bfdbfe\",\n \"#93c5fd\",\n \"#60a5fa\",\n \"#3b82f6\",\n \"#2563eb\",\n \"#1d4ed8\",\n \"#1e40af\",\n \"#1e3a8a\",\n \"#172554\",\n ),\n green: scale(\n \"#f0fdf4\",\n \"#dcfce7\",\n \"#bbf7d0\",\n \"#86efac\",\n \"#4ade80\",\n \"#22c55e\",\n \"#16a34a\",\n \"#15803d\",\n \"#166534\",\n \"#14532d\",\n \"#052e16\",\n ),\n red: scale(\n \"#fef2f2\",\n \"#fee2e2\",\n \"#fecaca\",\n \"#fca5a5\",\n \"#f87171\",\n \"#ef4444\",\n \"#dc2626\",\n \"#b91c1c\",\n \"#991b1b\",\n \"#7f1d1d\",\n \"#450a0a\",\n ),\n teal: scale(\n \"#f0fdfa\",\n \"#ccfbf1\",\n \"#99f6e4\",\n \"#5eead4\",\n \"#2dd4bf\",\n \"#14b8a6\",\n \"#0d9488\",\n \"#0f766e\",\n \"#115e59\",\n \"#134e4a\",\n \"#042f2e\",\n ),\n amber: scale(\n \"#fffbeb\",\n \"#fef3c7\",\n \"#fde68a\",\n \"#fcd34d\",\n \"#fbbf24\",\n \"#f59e0b\",\n \"#d97706\",\n \"#b45309\",\n \"#92400e\",\n \"#78350f\",\n \"#451a03\",\n ),\n yellow: scale(\n \"#fefce8\",\n \"#fef9c3\",\n \"#fef08a\",\n \"#fde047\",\n \"#facc15\",\n \"#eab308\",\n \"#ca8a04\",\n \"#a16207\",\n \"#854d0e\",\n \"#713f12\",\n \"#422006\",\n ),\n purple: scale(\n \"#faf5ff\",\n \"#f3e8ff\",\n \"#e9d5ff\",\n \"#d8b4fe\",\n \"#c084fc\",\n \"#a855f7\",\n \"#9333ea\",\n \"#7e22ce\",\n \"#6b21a8\",\n \"#581c87\",\n \"#3b0764\",\n ),\n orange: scale(\n \"#fff7ed\",\n \"#ffedd5\",\n \"#fed7aa\",\n \"#fdba74\",\n \"#fb923c\",\n \"#f97316\",\n \"#ea580c\",\n \"#c2410c\",\n \"#9a3412\",\n \"#7c2d12\",\n \"#431407\",\n ),\n white: \"#ffffff\",\n black: \"#000000\",\n} as const;\n\nexport type Palette = typeof palette;\n","// Layer-based semantic color system + syntax/terminal maps per design-system.md § Colors.\n\nimport { contrastForeground, lighten } from \"./color-utils.js\";\nimport { palette } from \"./palette.js\";\n\n// Highlight token → color map (see timeline-rendering.md § Syntax highlighting).\nexport type SyntaxColors = {\n keyword: string;\n string: string;\n number: string;\n boolean: string;\n comment: string;\n function: string;\n variable: string;\n type: string;\n class: string;\n constant: string;\n operator: string;\n punctuation: string;\n tag: string;\n attribute: string;\n property: string;\n regexp: string;\n escape: string;\n heading: string;\n link: string;\n deleted: string;\n inserted: string;\n};\n\n// Full xterm theme: bg/fg/cursor/selection + 16 ANSI colors.\nexport type TerminalTheme = {\n background: string;\n foreground: string;\n cursor: string;\n cursorAccent: string;\n selectionBackground: string;\n black: string;\n red: string;\n green: string;\n yellow: string;\n blue: string;\n magenta: string;\n cyan: string;\n white: string;\n brightBlack: string;\n brightRed: string;\n brightGreen: string;\n brightYellow: string;\n brightBlue: string;\n brightMagenta: string;\n brightCyan: string;\n brightWhite: string;\n};\n\nexport type ThemeColors = {\n // Surfaces (elevation ramp)\n surface0: string;\n surface1: string;\n surface2: string;\n surface3: string;\n surface4: string;\n // Surfaces (special)\n surfaceDiffEmpty: string;\n surfaceSidebar: string;\n surfaceSidebarHover: string;\n surfaceWorkspace: string;\n // Text\n foreground: string;\n foregroundMuted: string;\n // Brand\n accent: string;\n accentBright: string;\n accentForeground: string;\n // Semantic\n destructive: string;\n destructiveForeground: string;\n success: string;\n successForeground: string;\n // Borders\n border: string;\n borderAccent: string;\n // Status\n statusSuccess: string;\n statusDanger: string;\n statusWarning: string;\n statusMerged: string;\n // Diff\n diffAddition: string;\n diffDeletion: string;\n // Controls\n scrollbarHandle: string;\n // Legacy aliases (migration only; new code uses semantic tokens above)\n background: string;\n popover: string;\n popoverForeground: string;\n primary: string;\n secondary: string;\n muted: string;\n mutedForeground: string;\n input: string;\n ring: string;\n // Nested raw scales\n palette: typeof palette;\n syntax: SyntaxColors;\n terminal: TerminalTheme;\n};\n\n// Signal status colors — semantic success/danger/warning/merged. Kept a step\n// darker than the raw palette so they read as signals, not neon. Per Paseo\n// (`~/DEV/paseo/packages/app/src/styles/theme.ts`) light and dark differ.\nconst DARK_STATUS = {\n statusSuccess: \"#16a34a\", // green-600\n statusDanger: \"#dc2626\", // red-600\n statusWarning: \"#f59e0b\", // amber-500\n statusMerged: \"#9333ea\", // purple-600\n} as const;\nconst LIGHT_STATUS = {\n statusSuccess: \"#15803d\", // green-700\n statusDanger: \"#b91c1c\", // red-700\n statusWarning: \"#d97706\", // amber-600\n statusMerged: \"#7c3aed\", // purple-600\n} as const;\n\nfunction darkSyntax(): SyntaxColors {\n return {\n keyword: palette.purple[\"300\"],\n string: palette.green[\"300\"],\n number: palette.orange[\"300\"],\n boolean: palette.orange[\"300\"],\n comment: palette.zinc[\"500\"],\n function: palette.blue[\"300\"],\n variable: palette.zinc[\"200\"],\n type: palette.teal[\"300\"],\n class: palette.amber[\"300\"],\n constant: palette.orange[\"300\"],\n operator: palette.zinc[\"400\"],\n punctuation: palette.zinc[\"400\"],\n tag: palette.red[\"300\"],\n attribute: palette.amber[\"300\"],\n property: palette.blue[\"200\"],\n regexp: palette.teal[\"300\"],\n escape: palette.orange[\"200\"],\n heading: palette.blue[\"300\"],\n link: palette.blue[\"400\"],\n deleted: palette.red[\"400\"],\n inserted: palette.green[\"400\"],\n };\n}\n\nfunction lightSyntax(): SyntaxColors {\n return {\n keyword: palette.purple[\"700\"],\n string: palette.green[\"700\"],\n number: palette.orange[\"700\"],\n boolean: palette.orange[\"700\"],\n comment: palette.zinc[\"500\"],\n function: palette.blue[\"700\"],\n variable: palette.zinc[\"800\"],\n type: palette.teal[\"700\"],\n class: palette.amber[\"700\"],\n constant: palette.orange[\"700\"],\n operator: palette.zinc[\"600\"],\n punctuation: palette.zinc[\"600\"],\n tag: palette.red[\"700\"],\n attribute: palette.amber[\"700\"],\n property: palette.blue[\"700\"],\n regexp: palette.teal[\"700\"],\n escape: palette.orange[\"700\"],\n heading: palette.blue[\"700\"],\n link: palette.blue[\"600\"],\n deleted: palette.red[\"700\"],\n inserted: palette.green[\"700\"],\n };\n}\n\n// Tint config consumed by the dark-theme builder.\nexport type DarkTintConfig = {\n surface0: string;\n surface1: string;\n surface2: string;\n surface3: string;\n surface4: string;\n surfaceDiffEmpty: string;\n surfaceSidebar: string;\n surfaceSidebarHover: string;\n mutedForeground: string;\n scrollbarHandle: string;\n border: string;\n borderAccent: string;\n accent: string;\n accentBright: string;\n destructive: string;\n};\n\nfunction terminalFrom(surface0: string, foreground: string, accent: string): TerminalTheme {\n return {\n background: surface0,\n foreground,\n cursor: accent,\n cursorAccent: surface0,\n selectionBackground: \"rgba(255,255,255,0.18)\",\n black: palette.zinc[\"900\"],\n red: palette.red[\"500\"],\n green: palette.green[\"500\"],\n yellow: palette.amber[\"500\"],\n blue: palette.blue[\"500\"],\n magenta: palette.purple[\"500\"],\n cyan: palette.teal[\"500\"],\n white: palette.zinc[\"300\"],\n brightBlack: palette.zinc[\"600\"],\n brightRed: palette.red[\"400\"],\n brightGreen: palette.green[\"400\"],\n brightYellow: palette.amber[\"400\"],\n brightBlue: palette.blue[\"400\"],\n brightMagenta: palette.purple[\"400\"],\n brightCyan: palette.teal[\"400\"],\n brightWhite: palette.zinc[\"50\"],\n };\n}\n\n// Build a full dark theme color set from a tint config.\nexport function buildDarkColors(tint: DarkTintConfig): ThemeColors {\n // Paseo dark foreground is zinc-50 (#fafafa), not zinc-100.\n const foreground = palette.zinc[\"50\"];\n const accentForeground = contrastForeground(tint.accent);\n return {\n surface0: tint.surface0,\n surface1: tint.surface1,\n surface2: tint.surface2,\n surface3: tint.surface3,\n surface4: tint.surface4,\n surfaceDiffEmpty: tint.surfaceDiffEmpty,\n surfaceSidebar: tint.surfaceSidebar,\n surfaceSidebarHover: tint.surfaceSidebarHover,\n // Paseo dark: the workspace surface is surface1 (not the app background surface0).\n surfaceWorkspace: tint.surface1,\n foreground,\n foregroundMuted: tint.mutedForeground,\n accent: tint.accent,\n accentBright: tint.accentBright,\n accentForeground,\n destructive: tint.destructive,\n destructiveForeground: palette.white,\n // Paseo: success uses the theme accent in dark.\n success: tint.accent,\n successForeground: palette.white,\n border: tint.border,\n borderAccent: tint.borderAccent,\n ...DARK_STATUS,\n diffAddition: palette.green[\"400\"], // #4ade80\n diffDeletion: palette.red[\"500\"], // #ef4444\n scrollbarHandle: tint.scrollbarHandle,\n // Legacy aliases → nearest semantic token\n background: tint.surface0,\n popover: tint.surface2,\n popoverForeground: foreground,\n primary: tint.accent,\n secondary: tint.surface2,\n muted: tint.surface2,\n mutedForeground: tint.mutedForeground,\n input: tint.surface2,\n ring: tint.accent,\n palette,\n syntax: darkSyntax(),\n terminal: terminalFrom(tint.surface0, foreground, tint.accent),\n };\n}\n\n// The single light theme (built directly, not via the dark builder).\n// Values mirror Paseo's `lightSemanticColors` (`~/DEV/paseo/.../styles/theme.ts`).\nexport function buildLightColors(): ThemeColors {\n const foreground = \"#1a1a1e\";\n const accent = \"#253e6f\";\n return {\n surface0: \"#ffffff\",\n surface1: \"#fafafa\",\n surface2: \"#f4f4f5\",\n surface3: \"#e4e4e7\",\n surface4: \"#d4d4d8\",\n surfaceDiffEmpty: \"#f6f6f6\",\n surfaceSidebar: \"#f4f4f5\",\n surfaceSidebarHover: \"#e9e9ec\",\n surfaceWorkspace: \"#ffffff\",\n foreground,\n foregroundMuted: \"#71717a\",\n accent,\n accentBright: \"#2f4f8e\",\n accentForeground: contrastForeground(accent),\n destructive: \"#b04138\",\n destructiveForeground: \"#ffffff\",\n success: accent,\n successForeground: \"#ffffff\",\n border: \"#e4e4e7\",\n borderAccent: \"#ececf1\",\n ...LIGHT_STATUS,\n diffAddition: \"#15803d\", // green-700\n diffDeletion: \"#b91c1c\", // red-700\n scrollbarHandle: \"#3f3f46\", // zinc-700\n background: \"#ffffff\",\n popover: \"#ffffff\",\n popoverForeground: foreground,\n primary: accent,\n secondary: \"#f4f4f5\",\n muted: \"#f4f4f5\",\n mutedForeground: \"#71717a\",\n input: \"#f4f4f5\",\n ring: accent,\n palette,\n syntax: lightSyntax(),\n terminal: {\n ...terminalFrom(\"#ffffff\", foreground, accent),\n selectionBackground: \"rgba(0,0,0,0.15)\",\n white: \"#ffffff\",\n brightWhite: \"#fafafa\",\n },\n };\n}\n\nexport { lighten };\n","// Six named theme variants per design-system.md § Theme variants.\n// One light + five dark tints sharing the buildDarkColors builder.\n\nimport { buildDarkColors, buildLightColors, type ThemeColors } from \"./colors.js\";\n\n// theme-name → swatch color map (for Appearance picker).\nexport const THEME_SWATCHES: Readonly<Record<ThemeName, string>> = {\n light: \"#ffffff\",\n dark: \"#3b62b0\",\n zinc: \"#808080\",\n midnight: \"#4A6BA8\",\n claude: \"#D97757\",\n ghostty: \"#8caaee\",\n};\n\nexport type ThemeName = \"light\" | \"dark\" | \"zinc\" | \"midnight\" | \"claude\" | \"ghostty\";\nexport const THEME_NAMES: readonly ThemeName[] = [\n \"light\",\n \"dark\",\n \"zinc\",\n \"midnight\",\n \"claude\",\n \"ghostty\",\n];\n\nfunction buildVariantColors(name: ThemeName): ThemeColors {\n if (name === \"light\") return buildLightColors();\n\n switch (name) {\n // Paseo — subtle teal-green tint (default). Exact values from Paseo `paseoDarkColors`.\n case \"dark\":\n return buildDarkColors({\n surface0: \"#181B1A\",\n surface1: \"#1E2120\",\n surface2: \"#272A29\",\n surface3: \"#434645\",\n surface4: \"#595B5B\",\n surfaceDiffEmpty: \"#252827\",\n surfaceSidebar: \"#141716\",\n surfaceSidebarHover: \"#1c1f1e\",\n mutedForeground: \"#A1A5A4\",\n scrollbarHandle: \"#717574\",\n border: \"#252B2A\",\n borderAccent: \"#2f3135\",\n accent: \"#2e5cb8\",\n accentBright: \"#a2b4d7\",\n destructive: \"#c64f43\",\n });\n\n // Zinc — neutral gray, no tint. Exact values from Paseo `zincDarkColors`.\n case \"zinc\":\n return buildDarkColors({\n surface0: \"#18181b\",\n surface1: \"#1f1f22\",\n surface2: \"#27272a\",\n surface3: \"#3f3f46\",\n surface4: \"#52525b\",\n surfaceDiffEmpty: \"#242427\",\n surfaceSidebar: \"#131316\",\n surfaceSidebarHover: \"#1b1b1e\",\n mutedForeground: \"#a1a1aa\",\n scrollbarHandle: \"#71717a\",\n border: \"#27272a\",\n borderAccent: \"#303036\",\n accent: \"#e4e4e7\",\n accentBright: \"#fafafa\",\n destructive: \"#c44a4a\",\n });\n\n // Midnight — subtle blue tint. Exact values from Paseo `midnightDarkColors`.\n case \"midnight\":\n return buildDarkColors({\n surface0: \"#161820\",\n surface1: \"#1c1e27\",\n surface2: \"#252731\",\n surface3: \"#3c3e4c\",\n surface4: \"#535564\",\n surfaceDiffEmpty: \"#222430\",\n surfaceSidebar: \"#121420\",\n surfaceSidebarHover: \"#1a1c28\",\n mutedForeground: \"#9a9db0\",\n scrollbarHandle: \"#6b6e82\",\n border: \"#242636\",\n borderAccent: \"#2e3040\",\n accent: \"#3b6fcf\",\n accentBright: \"#7eaaeb\",\n destructive: \"#c44a52\",\n });\n\n // Claude — warm neutral with subtle orange undertone. From Paseo `claudeDarkColors`.\n case \"claude\":\n return buildDarkColors({\n surface0: \"#1f1f1e\",\n surface1: \"#262523\",\n surface2: \"#2f2d2b\",\n surface3: \"#4a4745\",\n surface4: \"#605d5b\",\n surfaceDiffEmpty: \"#2a2826\",\n surfaceSidebar: \"#1a1918\",\n surfaceSidebarHover: \"#222120\",\n mutedForeground: \"#ada9a5\",\n scrollbarHandle: \"#78746f\",\n border: \"#2c2a27\",\n borderAccent: \"#36332f\",\n accent: \"#d97757\",\n accentBright: \"#e89a7f\",\n destructive: \"#cf513e\",\n });\n\n // Ghostty — blue-tinted dark. Exact values from Paseo `ghosttyDarkColors`.\n case \"ghostty\":\n return buildDarkColors({\n surface0: \"#282c34\",\n surface1: \"#2f333d\",\n surface2: \"#383c48\",\n surface3: \"#4a4f5e\",\n surface4: \"#5b6175\",\n surfaceDiffEmpty: \"#323643\",\n surfaceSidebar: \"#21252d\",\n surfaceSidebarHover: \"#292d36\",\n mutedForeground: \"#c8ccd8\",\n scrollbarHandle: \"#a0a4b2\",\n border: \"#353a47\",\n borderAccent: \"#3f4454\",\n accent: \"#89b4fa\",\n accentBright: \"#b4d0fc\",\n destructive: \"#c44a55\",\n });\n }\n}\n\nexport type ThemeVariant = {\n name: ThemeName;\n colorScheme: \"light\" | \"dark\";\n colors: ThemeColors;\n swatch: string;\n};\n\nconst _variants: Record<ThemeName, ThemeVariant> = {} as Record<ThemeName, ThemeVariant>;\nfor (const name of THEME_NAMES) {\n const colorScheme: \"light\" | \"dark\" = name === \"light\" ? \"light\" : \"dark\";\n _variants[name] = {\n name,\n colorScheme,\n colors: buildVariantColors(name),\n swatch: THEME_SWATCHES[name],\n };\n}\nexport const THEME_VARIANTS: Readonly<Record<ThemeName, ThemeVariant>> = _variants;\n","// Theme scale tokens (theme-invariant unless patched by Appearance settings).\n// Exact values per design-system.md § Scales.\n\n// spacing: 4px base grid.\nexport const spacing = {\n \"0\": 0,\n \"1\": 4,\n \"1.5\": 6,\n \"2\": 8,\n \"3\": 12,\n \"4\": 16,\n \"6\": 24,\n \"8\": 32,\n \"12\": 48,\n \"16\": 64,\n \"20\": 80,\n \"24\": 96,\n \"32\": 128,\n} as const;\nexport type Spacing = typeof spacing;\nexport type SpacingKey = keyof Spacing;\n\n// fontSize keys — values deliberately typed as `number` (not literals) so the appearance\n// updater can patch them at runtime. `baseFontSize` holds the documented defaults.\nexport type FontSizeKey =\n | \"xs\"\n | \"code\"\n | \"sm\"\n | \"base\"\n | \"lg\"\n | \"xl\"\n | \"2xl\"\n | \"3xl\"\n | \"4xl\";\n\nexport const baseFontSize: Readonly<Record<FontSizeKey, number>> = {\n xs: 12,\n code: 12,\n sm: 14,\n base: 16,\n lg: 18,\n xl: 20,\n \"2xl\": 22,\n \"3xl\": 26,\n \"4xl\": 34,\n};\n\n// fontWeight — RN-style string weights.\nexport const fontWeight = {\n normal: \"400\",\n medium: \"500\",\n semibold: \"600\",\n bold: \"700\",\n} as const;\nexport type FontWeight = typeof fontWeight;\n\nexport const borderRadius = {\n none: 0,\n sm: 2,\n base: 4,\n md: 6,\n lg: 8,\n xl: 12,\n \"2xl\": 16,\n full: 9999,\n} as const;\nexport type BorderRadius = typeof borderRadius;\n\nexport const borderWidth = {\n \"0\": 0,\n \"1\": 1,\n \"2\": 2,\n} as const;\nexport type BorderWidth = typeof borderWidth;\n\nexport const opacity = {\n \"0\": 0,\n \"50\": 0.5,\n \"100\": 1,\n} as const;\nexport type Opacity = typeof opacity;\n\nexport const iconSize = {\n xs: 12,\n sm: 14,\n md: 16,\n lg: 20,\n} as const;\nexport type IconSize = typeof iconSize;\n\n// lineHeight.diff is reserved for code/diff; prose uses fontSize.base * ~1.4 at render time.\nexport const baseLineHeight = { diff: 22 } as const;\n\n// Default platform font stacks (seeds; appearance can override the `ui` stack).\nexport const DEFAULT_UI_FONT =\n 'system-ui, -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif';\nexport const DEFAULT_MONO_FONT =\n 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace';\n\nexport type Shadow = {\n color: string;\n offsetX: number;\n offsetY: number;\n radius: number;\n elevation: number;\n};\n\nexport type ShadowSet = { sm: Shadow; md: Shadow; lg: Shadow };\n\n// Shadow builder — dark themes use a stronger alpha-tinted color.\nexport function buildShadows(colorScheme: \"light\" | \"dark\"): ShadowSet {\n const color = colorScheme === \"dark\" ? \"rgba(0,0,0,0.55)\" : \"rgba(24,24,27,0.12)\";\n return {\n sm: { color, offsetX: 0, offsetY: 1, radius: 2, elevation: 1 },\n md: { color, offsetX: 0, offsetY: 3, radius: 8, elevation: 4 },\n lg: { color, offsetX: 0, offsetY: 10, radius: 24, elevation: 12 },\n };\n}\n","// Full Theme type + runtime appearance application.\n// design-system.md § Theme token shape, § Behavior (appearance application)\n\nimport {\n baseFontSize,\n baseLineHeight,\n borderRadius,\n borderWidth,\n buildShadows,\n DEFAULT_MONO_FONT,\n DEFAULT_UI_FONT,\n fontWeight,\n iconSize,\n opacity,\n spacing,\n type FontSizeKey,\n type ShadowSet,\n} from \"./tokens.js\";\nimport { type ThemeColors } from \"./colors.js\";\nimport { type ThemeName, THEME_VARIANTS } from \"./variants.js\";\n\nexport type Theme = {\n name: ThemeName;\n colorScheme: \"light\" | \"dark\";\n colors: ThemeColors;\n spacing: typeof spacing;\n // fontSize is Record<FontSizeKey, number> (not readonly narrowed literals) so appearance\n // patch can replace values without a type error.\n fontSize: Record<FontSizeKey, number>;\n fontFamily: {\n // Both are plain strings so custom fonts can be spliced in at runtime.\n ui: string;\n mono: string;\n };\n lineHeight: Record<\"diff\", number>;\n iconSize: typeof iconSize;\n fontWeight: typeof fontWeight;\n borderRadius: typeof borderRadius;\n borderWidth: typeof borderWidth;\n opacity: typeof opacity;\n shadow: ShadowSet;\n swatch: string;\n};\n\nfunction buildTheme(name: ThemeName): Theme {\n const variant = THEME_VARIANTS[name];\n return {\n name,\n colorScheme: variant.colorScheme,\n colors: variant.colors,\n spacing,\n fontSize: { ...baseFontSize },\n fontFamily: {\n ui: DEFAULT_UI_FONT,\n mono: DEFAULT_MONO_FONT,\n },\n lineHeight: { ...baseLineHeight },\n iconSize,\n fontWeight,\n borderRadius,\n borderWidth,\n opacity,\n shadow: buildShadows(variant.colorScheme),\n swatch: variant.swatch,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Appearance settings + runtime patch\n// ---------------------------------------------------------------------------\n\nexport type AppearanceSettings = {\n /** One of the six named theme variants. */\n themeName?: ThemeName;\n /** Custom UI font family string; empty/undefined → platform default. */\n uiFont?: string;\n /** Custom mono font family string; empty/undefined → platform default. */\n monoFont?: string;\n /**\n * Desired base font size (px). All fontSize tokens are scaled relative to the\n * documented base of 16px. Clamped 10–24.\n */\n fontSize?: number;\n};\n\nconst FONT_SIZE_BASE = 16;\nconst FONT_SIZE_MIN = 10;\nconst FONT_SIZE_MAX = 24;\n\n/**\n * Apply appearance settings to a base theme and return a new patched theme.\n * The base theme object is never mutated.\n * Code/mono surfaces keep the mono font because they reference `fontFamily.mono`\n * independently — a custom `uiFont` only patches `fontFamily.ui`.\n */\nexport function applyAppearance(base: Theme, settings: AppearanceSettings): Theme {\n const themeName: ThemeName = settings.themeName ?? base.name;\n const baseTheme = buildTheme(themeName);\n\n const uiFont =\n settings.uiFont && settings.uiFont.trim() !== \"\" ? settings.uiFont : DEFAULT_UI_FONT;\n const monoFont =\n settings.monoFont && settings.monoFont.trim() !== \"\" ? settings.monoFont : DEFAULT_MONO_FONT;\n\n let fontSize = { ...baseTheme.fontSize };\n if (settings.fontSize != null) {\n const clamped = Math.max(FONT_SIZE_MIN, Math.min(FONT_SIZE_MAX, settings.fontSize));\n const scale = clamped / FONT_SIZE_BASE;\n const key: FontSizeKey[] = [\"xs\", \"code\", \"sm\", \"base\", \"lg\", \"xl\", \"2xl\", \"3xl\", \"4xl\"];\n for (const k of key) {\n const base = baseFontSize[k];\n // `code` always tracks the xs/code document base, not the prose base, to avoid\n // code surfaces growing disproportionately.\n fontSize[k] = Math.round(base * scale);\n }\n }\n\n return {\n ...baseTheme,\n fontSize,\n fontFamily: { ui: uiFont, mono: monoFont },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Theme registry / accessor\n// ---------------------------------------------------------------------------\n\n/** Build a fresh Theme for the given name with default appearance settings. */\nexport function getTheme(name: ThemeName): Theme {\n return buildTheme(name);\n}\n\nexport const DEFAULT_THEME_NAME: ThemeName = \"dark\";\nexport { type ThemeName };\n","/**\n * Theme → CSS custom properties bridge.\n * Flattens a Theme object into `--pi-*` CSS variables on :root.\n * design-system.md § Purpose (render-stack decision)\n */\n\nimport { type Theme } from \"./theme.js\";\nimport { type ThemeColors, type SyntaxColors, type TerminalTheme } from \"./colors.js\";\n\n// ---------------------------------------------------------------------------\n// Token → variable-name map\n// ---------------------------------------------------------------------------\n\n/**\n * Given a Theme, produces a flat Record<`--pi-${path}`, value> map\n * suitable for setting on `document.documentElement.style`.\n */\nexport function flattenThemeToVars(theme: Theme): Record<string, string> {\n const vars: Record<string, string> = {};\n\n // Colors (flat semantic tokens)\n flattenColors(theme.colors, vars);\n\n // Spacing\n for (const [key, val] of Object.entries(theme.spacing)) {\n vars[`--pi-spacing-${key}`] = `${val}px`;\n }\n\n // Font size\n for (const [key, val] of Object.entries(theme.fontSize)) {\n vars[`--pi-font-size-${key}`] = `${val}px`;\n }\n\n // Font family\n vars[\"--pi-font-ui\"] = theme.fontFamily.ui;\n vars[\"--pi-font-mono\"] = theme.fontFamily.mono;\n\n // Line height\n vars[\"--pi-line-height-diff\"] = `${theme.lineHeight.diff}px`;\n\n // Icon size\n for (const [key, val] of Object.entries(theme.iconSize)) {\n vars[`--pi-icon-size-${key}`] = `${val}px`;\n }\n\n // Font weight\n for (const [key, val] of Object.entries(theme.fontWeight)) {\n vars[`--pi-font-weight-${key}`] = String(val);\n }\n\n // Border radius\n for (const [key, val] of Object.entries(theme.borderRadius)) {\n vars[`--pi-radius-${key}`] = val === 9999 ? \"9999px\" : `${val}px`;\n }\n\n // Border width\n for (const [key, val] of Object.entries(theme.borderWidth)) {\n vars[`--pi-border-width-${key}`] = `${val}px`;\n }\n\n // Opacity\n for (const [key, val] of Object.entries(theme.opacity)) {\n vars[`--pi-opacity-${key}`] = String(val);\n }\n\n // Shadow (convert { offsetX, offsetY, radius, color } → CSS box-shadow string)\n for (const [key, val] of Object.entries(theme.shadow)) {\n if (typeof val === \"string\") {\n vars[`--pi-shadow-${key}`] = val;\n } else if (val && typeof val === \"object\") {\n const s = val as { offsetX: number; offsetY: number; radius: number; color: string };\n vars[`--pi-shadow-${key}`] = `${s.offsetX}px ${s.offsetY}px ${s.radius}px ${s.color}`;\n }\n }\n\n // Color scheme marker (for prefers-color-scheme media query override)\n vars[\"--pi-color-scheme\"] = theme.colorScheme;\n\n return vars;\n}\n\nfunction flattenColors(colors: ThemeColors, vars: Record<string, string>): void {\n // Top-level semantic color tokens\n const {\n palette: _palette,\n syntax: _syntax,\n terminal: _terminal,\n ...semanticColors\n } = colors;\n\n // Emit color tokens VERBATIM (camelCase), e.g. `--pi-color-surfaceSidebar`,\n // `--pi-color-foregroundMuted`, `--pi-color-statusDanger`. This matches how\n // every component references them; kebab-casing here silently broke all\n // multi-word tokens (they fell back to defaults).\n for (const [key, val] of Object.entries(semanticColors)) {\n if (typeof val === \"string\") {\n vars[`--pi-color-${key}`] = val;\n }\n }\n\n // Syntax colors → --syntax-*\n if (_syntax) {\n flattenSyntax(_syntax, vars);\n }\n\n // Terminal colors are not CSS variables (consumed by xterm config directly);\n // but we expose a few key ones for CSS terminal-wrapper styling.\n if (_terminal) {\n vars[\"--pi-terminal-bg\"] = _terminal.background;\n vars[\"--pi-terminal-fg\"] = _terminal.foreground;\n }\n}\n\nfunction flattenSyntax(syntax: SyntaxColors, vars: Record<string, string>): void {\n for (const [key, val] of Object.entries(syntax)) {\n if (typeof val === \"string\") {\n vars[`--syntax-${camelToKebab(key)}`] = val;\n }\n }\n}\n\n/** Convert camelCase to kebab-case. */\nfunction camelToKebab(str: string): string {\n return str.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);\n}\n\n// ---------------------------------------------------------------------------\n// DOM application\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a flattened variable map to the document root.\n * Idempotent — re-applying the same map is a no-op (DOM diffing).\n */\nexport function applyVarsToRoot(vars: Record<string, string>): void {\n if (typeof document === \"undefined\") return;\n const style = document.documentElement.style;\n for (const [prop, val] of Object.entries(vars)) {\n style.setProperty(prop, val);\n }\n // Set color-scheme for native form control theming\n const scheme = vars[\"--pi-color-scheme\"];\n if (scheme) {\n document.documentElement.style.setProperty(\"color-scheme\", scheme);\n }\n}\n\n/**\n * Full pipeline: flatten a Theme and apply to the DOM.\n */\nexport function applyThemeToDOM(theme: Theme): void {\n const vars = flattenThemeToVars(theme);\n applyVarsToRoot(vars);\n}\n","// Brand config schema + resolver.\n// features/white-label-branding.md § Public Contract, § Resolution\n\nimport { z } from \"zod\";\nimport { contrastForeground, isHexColor, lighten } from \"@pi-studio-ui/theme/color-utils.js\";\n\n// ---------------------------------------------------------------------------\n// Schema\n// ---------------------------------------------------------------------------\n\nconst AssetRefSchema = z.string().min(1, \"Asset path must not be empty\");\n\nconst BrandColorsSchema = z.object({\n accent: z.string().refine(isHexColor, { message: \"accent must be a valid #rrggbb hex color\" }),\n accentBright: z\n .string()\n .refine(isHexColor, { message: \"accentBright must be a valid #rrggbb hex color\" })\n .optional(),\n accentForeground: z\n .string()\n .refine(isHexColor, { message: \"accentForeground must be a valid #rrggbb hex color\" })\n .optional(),\n swatch: z\n .string()\n .refine(isHexColor, { message: \"swatch must be a valid #rrggbb hex color\" })\n .optional(),\n});\n\nconst BrandAssetsSchema = z.object({\n logoLight: AssetRefSchema,\n logoDark: AssetRefSchema,\n logoMark: AssetRefSchema.optional(),\n icon: AssetRefSchema,\n splash: AssetRefSchema.optional(),\n});\n\nconst BrandLinksSchema = z\n .object({\n docs: z.string().url().optional(),\n support: z.string().url().optional(),\n issues: z.string().url().optional(),\n website: z.string().url().optional(),\n })\n .optional();\n\nconst BrandLegalSchema = z\n .object({\n copyright: z.string().optional(),\n companyName: z.string().optional(),\n })\n .optional();\n\nexport const BrandConfigSchema = z.object({\n productName: z.string().min(1, \"productName is required\"),\n shortName: z.string().optional(),\n tagline: z.string().optional(),\n colors: BrandColorsSchema,\n assets: BrandAssetsSchema,\n links: BrandLinksSchema,\n legal: BrandLegalSchema,\n});\n\nexport type BrandConfig = z.infer<typeof BrandConfigSchema>;\n\n// ---------------------------------------------------------------------------\n// Derived brand colors (fill in optional accent fields if omitted)\n// ---------------------------------------------------------------------------\n\nexport type ResolvedBrandColors = Required<\n Pick<BrandConfig[\"colors\"], \"accent\" | \"accentBright\" | \"accentForeground\" | \"swatch\">\n>;\n\nexport function resolveAccentColors(colors: BrandConfig[\"colors\"]): ResolvedBrandColors {\n const accent = colors.accent;\n const accentBright = colors.accentBright ?? lighten(accent, 0.35);\n const accentForeground = colors.accentForeground ?? contrastForeground(accent);\n const swatch = colors.swatch ?? accent;\n return { accent, accentBright, accentForeground, swatch };\n}\n\n// ---------------------------------------------------------------------------\n// Default Pi-Studio brand\n// ---------------------------------------------------------------------------\n\nexport const DEFAULT_BRAND: BrandConfig = {\n productName: \"Pi-Studio\",\n shortName: \"Pi-Studio\",\n tagline: \"Your local-first AI coding agent\",\n colors: {\n accent: \"#2e5cb8\",\n accentBright: \"#a2b4d7\",\n accentForeground: \"#ffffff\",\n swatch: \"#3b62b0\",\n },\n assets: {\n logoLight: \"brand/logo-light.svg\",\n logoDark: \"brand/logo-dark.svg\",\n logoMark: \"brand/logo-mark.svg\",\n icon: \"brand/icon.png\",\n splash: \"brand/splash.png\",\n },\n links: {\n docs: \"https://pi-studio.dev/docs\",\n issues: \"https://github.com/av-pi-studio/pi-studio/issues\",\n website: \"https://pi-studio.dev\",\n },\n legal: {\n companyName: \"Pi-Studio Contributors\",\n },\n};\n\n// ---------------------------------------------------------------------------\n// Resolver\n// ---------------------------------------------------------------------------\n\nexport type BrandResolution =\n | { ok: true; brand: BrandConfig; source: \"default\" | \"env\" }\n | { ok: false; errors: string[] };\n\n/**\n * Resolve the active brand config.\n *\n * At build time: reads PI_STUDIO_BRAND_DIR from the environment, loads and\n * validates <dir>/brand.config.json, fails fast with field-level errors.\n * When unset: returns the default Pi-Studio brand.\n *\n * In Node unit tests `rawConfig` is injected directly to skip file I/O.\n */\nexport function resolveBrandConfig(rawConfig?: unknown): BrandResolution {\n if (rawConfig === undefined) {\n // No injection → use built-in default.\n return { ok: true, brand: DEFAULT_BRAND, source: \"default\" };\n }\n\n const result = BrandConfigSchema.safeParse(rawConfig);\n if (!result.success) {\n const errors = result.error.errors.map(\n (e) => `${e.path.join(\".\")}: ${e.message}`,\n );\n return { ok: false, errors };\n }\n\n // Fill optional shortName default.\n const brand = result.data;\n if (!brand.shortName) brand.shortName = brand.productName;\n\n return { ok: true, brand, source: \"env\" };\n}\n\n// ---------------------------------------------------------------------------\n// Runtime accessor (frozen after first resolution)\n// ---------------------------------------------------------------------------\n\nlet _resolved: BrandConfig | null = null;\n\nexport function getActiveBrand(): BrandConfig {\n if (!_resolved) _resolved = DEFAULT_BRAND;\n return _resolved;\n}\n\n/** Inject a validated brand (used in tests and by the build-time bootstrap). */\nexport function _setActiveBrand(brand: BrandConfig): void {\n _resolved = brand;\n}\n\nexport function _resetActiveBrand(): void {\n _resolved = null;\n}\n","// Brand accent injection into theme variants.\n// design-system.md § Theme variants (Brand injection)\n// features/white-label-branding.md § Behavior & Algorithms\n\nimport { type ResolvedBrandColors, resolveAccentColors, type BrandConfig } from \"./config.js\";\nimport { type Theme } from \"@pi-studio-ui/theme/theme.js\";\nimport { type ThemeName, THEME_NAMES } from \"@pi-studio-ui/theme/variants.js\";\nimport { getTheme } from \"@pi-studio-ui/theme/theme.js\";\n\n/**\n * Build a branded copy of a theme by replacing only the accent family tokens\n * and swatch. All other tokens (surfaces, semantic status, syntax, terminal)\n * remain unchanged — only the accent family is brand-overridable in this version.\n */\nexport function injectBrandAccent(theme: Theme, resolved: ResolvedBrandColors): Theme {\n return {\n ...theme,\n colors: {\n ...theme.colors,\n accent: resolved.accent,\n accentBright: resolved.accentBright,\n accentForeground: resolved.accentForeground,\n // Legacy aliases that mirror accent.\n primary: resolved.accent,\n ring: resolved.accent,\n },\n swatch: resolved.swatch,\n };\n}\n\n/**\n * Build all six branded themes from a brand config.\n * Returns a map from theme name → patched Theme.\n */\nexport function buildBrandedThemes(\n brand: BrandConfig,\n): Readonly<Record<ThemeName, Theme>> {\n const resolved = resolveAccentColors(brand.colors);\n const result = {} as Record<ThemeName, Theme>;\n for (const name of THEME_NAMES) {\n result[name] = injectBrandAccent(getTheme(name), resolved);\n }\n return result;\n}\n","/**\n * Appearance store: persisted theme + appearance settings, system dark-mode following.\n * design-system.md § Theme variants, § Behavior (Appearance)\n */\n\nimport { type ThemeName, THEME_NAMES } from \"./variants.js\";\nimport { type AppearanceSettings, getTheme, applyAppearance, DEFAULT_THEME_NAME } from \"./theme.js\";\nimport { type Theme } from \"./theme.js\";\nimport { applyThemeToDOM } from \"./css-bridge.js\";\nimport { injectBrandAccent } from \"@pi-studio-ui/brand/theme-injection.js\";\nimport { resolveAccentColors, type BrandConfig } from \"@pi-studio-ui/brand/config.js\";\n\n// ---------------------------------------------------------------------------\n// KeyValueStore interface (localStorage on web, Electron bridge on desktop)\n// ---------------------------------------------------------------------------\n\nexport interface KeyValueStore {\n get(key: string): string | null;\n set(key: string, value: string): void;\n}\n\nconst STORAGE_KEY = \"pi-studio-appearance\";\n\n// ---------------------------------------------------------------------------\n// Appearance state\n// ---------------------------------------------------------------------------\n\nexport type AppearanceMode = ThemeName | \"system\";\n\nexport interface AppearanceState {\n mode: AppearanceMode;\n settings: AppearanceSettings;\n resolvedTheme: Theme;\n}\n\n// ---------------------------------------------------------------------------\n// Appearance controller\n// ---------------------------------------------------------------------------\n\nexport interface AppearanceController {\n getState(): AppearanceState;\n setMode(mode: AppearanceMode): void;\n updateSettings(patch: Partial<AppearanceSettings>): void;\n /** Call to apply current theme to DOM (call on init + changes). */\n apply(): void;\n /** Start system-follow listener; returns cleanup. */\n listen(): () => void;\n}\n\nexport function createAppearanceController(\n store: KeyValueStore,\n brandConfig?: BrandConfig,\n): AppearanceController {\n let state: AppearanceState = loadInitial(store);\n let mediaCleanup: (() => void) | null = null;\n\n function resolveThemeName(mode: AppearanceMode): ThemeName {\n if (mode === \"system\") {\n const prefersDark =\n typeof window !== \"undefined\" &&\n window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n return prefersDark ? \"dark\" : \"light\";\n }\n return mode;\n }\n\n function buildResolved(mode: AppearanceMode, settings: AppearanceSettings): Theme {\n const themeName = resolveThemeName(mode);\n const base = getTheme(themeName);\n let theme = applyAppearance(base, { ...settings, themeName });\n if (brandConfig?.colors) {\n const resolved = resolveAccentColors(brandConfig.colors);\n theme = injectBrandAccent(theme, resolved);\n }\n return theme;\n }\n\n function persist(): void {\n const serialized = JSON.stringify({ mode: state.mode, settings: state.settings });\n store.set(STORAGE_KEY, serialized);\n }\n\n function loadInitial(store: KeyValueStore): AppearanceState {\n const raw = store.get(STORAGE_KEY);\n let mode: AppearanceMode = DEFAULT_THEME_NAME;\n let settings: AppearanceSettings = {};\n if (raw) {\n try {\n const parsed = JSON.parse(raw);\n if (parsed.mode && (THEME_NAMES.includes(parsed.mode) || parsed.mode === \"system\")) {\n mode = parsed.mode;\n }\n if (parsed.settings && typeof parsed.settings === \"object\") {\n settings = parsed.settings;\n }\n } catch {\n // ignore corrupt data\n }\n }\n const resolvedTheme = buildResolved(mode, settings);\n return { mode, settings, resolvedTheme };\n }\n\n function update(): void {\n state = { ...state, resolvedTheme: buildResolved(state.mode, state.settings) };\n persist();\n }\n\n const controller: AppearanceController = {\n getState() {\n return state;\n },\n setMode(mode) {\n state = { ...state, mode };\n update();\n controller.apply();\n },\n updateSettings(patch) {\n state = { ...state, settings: { ...state.settings, ...patch } };\n update();\n controller.apply();\n },\n apply() {\n applyThemeToDOM(state.resolvedTheme);\n },\n listen() {\n if (typeof window === \"undefined\") return () => {};\n const mq = window.matchMedia(\"(prefers-color-scheme: dark)\");\n const handler = () => {\n if (state.mode === \"system\") {\n update();\n controller.apply();\n }\n };\n mq.addEventListener(\"change\", handler);\n mediaCleanup = () => mq.removeEventListener(\"change\", handler);\n return mediaCleanup;\n },\n };\n\n return controller;\n}\n","/**\n * ThemeBoundary — applies theme CSS variables before first paint.\n * Wraps the app so no flash of wrong theme occurs.\n */\n\nimport { useEffect, useRef, type ReactNode } from \"react\";\nimport {\n createAppearanceController,\n type AppearanceController,\n type KeyValueStore,\n} from \"./appearance-store.js\";\nimport { type BrandConfig } from \"@pi-studio-ui/brand/config.js\";\n\nexport interface ThemeBoundaryProps {\n store: KeyValueStore;\n brandConfig?: BrandConfig;\n children: ReactNode;\n}\n\n/**\n * Synchronously applies theme on first render, then listens for system changes.\n */\nexport function ThemeBoundary({ store, brandConfig, children }: ThemeBoundaryProps) {\n const controllerRef = useRef<AppearanceController | null>(null);\n\n if (!controllerRef.current) {\n controllerRef.current = createAppearanceController(store, brandConfig);\n // Apply synchronously before first paint\n controllerRef.current.apply();\n }\n\n useEffect(() => {\n const cleanup = controllerRef.current!.listen();\n return cleanup;\n }, []);\n\n return <>{children}</>;\n}\n","import { type KeyValueStore } from \"@pi-studio-ui/theme/appearance-store.js\";\n\n// Web KeyValueStore backed by localStorage. In Electron a bridge-backed store\n// can be substituted at the same DI boundary (ThemeBoundary `store` prop).\nexport const localKvStore: KeyValueStore = {\n get(key) {\n try {\n return globalThis.localStorage?.getItem(key) ?? null;\n } catch {\n return null;\n }\n },\n set(key, value) {\n try {\n globalThis.localStorage?.setItem(key, value);\n } catch {\n // Ignore quota / unavailable storage.\n }\n },\n};\n","import { type ReactNode } from \"react\";\nimport { QueryClientProvider } from \"@tanstack/react-query\";\nimport { queryClient } from \"@pi-studio-ui/lib/connection/query-client.js\";\nimport { ThemeBoundary } from \"@pi-studio-ui/theme/ThemeBoundary.js\";\nimport { localKvStore } from \"./kv-store.js\";\n\n// Top-level provider composition. ThemeBoundary applies `--pi-*` CSS variables\n// before first paint; TanStack Query owns server-cache state. Router and\n// connection context are added as their phases land.\nexport function AppProviders({ children }: { children: ReactNode }) {\n return (\n <ThemeBoundary store={localKvStore}>\n <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>\n </ThemeBoundary>\n );\n}\n","// Button primitive — variant/size/state contracts.\n// ui-components.md § Pressables\n\nexport type ButtonVariant = \"default\" | \"secondary\" | \"outline\" | \"ghost\" | \"destructive\";\nexport type ButtonSize = \"xs\" | \"sm\" | \"md\" | \"lg\";\n\n// Min-height per size (px) per ui-components.md: \"min-heights from 28\".\nexport const BUTTON_MIN_HEIGHT: Record<ButtonSize, number> = {\n xs: 28,\n sm: 32,\n md: 36,\n lg: 40,\n};\n\n// Icon size per button size (maps to iconSize token keys).\nexport const BUTTON_ICON_SIZE: Record<ButtonSize, number> = {\n xs: 12,\n sm: 14,\n md: 16,\n lg: 20,\n};\n\n// Horizontal padding per size.\nexport const BUTTON_PADDING_H: Record<ButtonSize, number> = {\n xs: 8,\n sm: 10,\n md: 12,\n lg: 16,\n};\n\n// Font size per size (px).\nexport const BUTTON_FONT_SIZE: Record<ButtonSize, number> = {\n xs: 12,\n sm: 13,\n md: 14,\n lg: 15,\n};\n\nexport type ButtonStateStyle = {\n opacity: number;\n /** Token key for background color. null = no bg (transparent). */\n bgToken: string | null;\n};\n\n/**\n * Resolve the opacity and background token for a button given its state.\n * - pressed → 0.85 opacity\n * - disabled or loading → 0.5 opacity\n * - ghost hover → special token swap (handled by caller reading `isGhostHover`)\n */\nexport function resolveButtonState(opts: {\n variant: ButtonVariant;\n pressed: boolean;\n disabled: boolean;\n loading: boolean;\n}): { opacity: number; isGhostHover: false } {\n const { pressed, disabled, loading } = opts;\n if (disabled || loading) return { opacity: 0.5, isGhostHover: false };\n if (pressed) return { opacity: 0.85, isGhostHover: false };\n return { opacity: 1, isGhostHover: false };\n}\n\n/**\n * Returns the correct text/icon color token key for each variant.\n * This is what callers pass to the theme-prop binder for icon color.\n */\nexport function buttonIconColorToken(variant: ButtonVariant): string {\n switch (variant) {\n case \"default\":\n return \"accentForeground\";\n case \"secondary\":\n return \"foreground\";\n case \"outline\":\n return \"accent\";\n case \"ghost\":\n return \"foregroundMuted\"; // swaps to \"foreground\" on hover\n case \"destructive\":\n return \"destructiveForeground\";\n }\n}\n\n/** Returns \"foreground\" when ghost is hovered, else the default ghost color. */\nexport function ghostHoverIconToken(isHovered: boolean): string {\n return isHovered ? \"foreground\" : \"foregroundMuted\";\n}\n","/**\n * Button — pressable with variant/size/state, styled from --pi-* CSS variables.\n * ui-components.md § Pressables\n */\n\nimport { type ReactNode, type ButtonHTMLAttributes } from \"react\";\nimport { clsx } from \"clsx\";\nimport { Loader2 } from \"lucide-react\";\nimport styles from \"./Button.module.css\";\nimport {\n type ButtonVariant,\n type ButtonSize,\n BUTTON_MIN_HEIGHT,\n BUTTON_PADDING_H,\n BUTTON_FONT_SIZE,\n BUTTON_ICON_SIZE,\n resolveButtonState,\n} from \"@pi-studio-ui/ui/button.js\";\n\nexport interface ButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, \"children\"> {\n variant?: ButtonVariant;\n size?: ButtonSize;\n loading?: boolean;\n leftIcon?: ReactNode;\n trailing?: ReactNode;\n iconOnly?: boolean;\n children?: ReactNode;\n}\n\nexport function Button({\n variant = \"default\",\n size = \"md\",\n loading = false,\n disabled = false,\n leftIcon,\n trailing,\n iconOnly = false,\n children,\n style,\n className,\n ...rest\n}: ButtonProps) {\n const { opacity } = resolveButtonState({\n variant,\n pressed: false,\n disabled: disabled ?? false,\n loading,\n });\n const isDisabled = disabled || loading;\n\n const iconPx = BUTTON_ICON_SIZE[size];\n\n return (\n <button\n className={clsx(\n styles.btn,\n styles[variant],\n iconOnly && styles.iconOnly,\n className,\n )}\n disabled={isDisabled}\n aria-busy={loading || undefined}\n style={{\n minHeight: BUTTON_MIN_HEIGHT[size],\n paddingLeft: iconOnly ? undefined : BUTTON_PADDING_H[size],\n paddingRight: iconOnly ? undefined : BUTTON_PADDING_H[size],\n fontSize: BUTTON_FONT_SIZE[size],\n opacity,\n ...(iconOnly ? { width: BUTTON_MIN_HEIGHT[size] } : {}),\n ...style,\n }}\n {...rest}\n >\n {loading ? (\n <Loader2 className={styles.loadingSpinner} size={iconPx} aria-hidden />\n ) : leftIcon ? (\n <span aria-hidden style={{ display: \"flex\", alignItems: \"center\", flexShrink: 0 }}>\n {leftIcon}\n </span>\n ) : null}\n {children}\n {trailing && !loading && (\n <span aria-hidden style={{ display: \"flex\", alignItems: \"center\", flexShrink: 0 }}>\n {trailing}\n </span>\n )}\n </button>\n );\n}\n","/**\n * TextInput / TextArea — form input controls.\n * ui-components.md § Inputs & form controls (AdaptiveTextInput)\n */\n\nimport {\n forwardRef,\n type InputHTMLAttributes,\n type TextareaHTMLAttributes,\n} from \"react\";\nimport { clsx } from \"clsx\";\nimport styles from \"./TextInput.module.css\";\n\n// ---- TextInput ----\n\nexport interface TextInputProps extends InputHTMLAttributes<HTMLInputElement> {\n className?: string;\n}\n\nexport const TextInput = forwardRef<HTMLInputElement, TextInputProps>(\n function TextInput({ className, ...rest }, ref) {\n return (\n <input\n ref={ref}\n className={clsx(styles.input, className)}\n {...rest}\n />\n );\n },\n);\n\n// ---- TextArea ----\n\nexport interface TextAreaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {\n className?: string;\n}\n\nexport const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(\n function TextArea({ className, ...rest }, ref) {\n return (\n <textarea\n ref={ref}\n className={clsx(styles.input, styles.textarea, className)}\n {...rest}\n />\n );\n },\n);\n","import { z } from \"zod\";\n\n/**\n * Shared validation conventions for Pi-Studio.\n *\n * Every wire frame and every on-disk JSON document is parsed through Zod before it is trusted\n * (MAIN-SCOPE.md §9). These helpers are the backbone of \"validate at every boundary\" and are\n * reused by the protocol message schemas and the file-based persistence stores.\n *\n * ## Append-only compatibility rules (always)\n *\n * Schemas are **append-only**. A 6-month-old client must still parse new-daemon messages and\n * vice-versa. Concretely:\n *\n * - **New fields are optional with a default or transform.** Use {@link optionalWithDefault} so a\n * missing field deserializes to a known value instead of `undefined`.\n * - **Never flip `optional` → `required`.** Once a field is optional it stays optional.\n * - **Never remove a field.** Stop *sending* it if you must, but keep *reading* it.\n * - **Never narrow a type** (e.g. `string` → enum, `nullable` → non-null). New enum values are\n * gated at serialization behind capability flags so old clients only ever receive values they\n * advertised support for.\n *\n * ## COMPAT(name) convention\n *\n * Back-compat shims are tagged so a single grep (`COMPAT(`) lists all pending cleanup work. Each\n * tag records the version the shim was added in and the date it may be removed. Use {@link COMPAT}\n * at the call site of a shim, e.g.:\n *\n * ```ts\n * // COMPAT(legacy-provider-key): added 1.2.0, remove by 2026-12-31\n * const normalized = COMPAT({ name: \"legacy-provider-key\", addedIn: \"1.2.0\", removeBy: \"2026-12-31\" });\n * ```\n */\n\n/** Metadata describing a back-compat shim. Grep `COMPAT(` to enumerate cleanup work. */\nexport interface CompatTag {\n /** Stable identifier for the shim (kebab-case). */\n readonly name: string;\n /** Version the shim was introduced in (semver). */\n readonly addedIn: string;\n /** ISO date (YYYY-MM-DD) after which the shim may be removed. */\n readonly removeBy: string;\n}\n\n/**\n * Marks a back-compat shim. Returns the tag unchanged so it can be used inline; its real value is\n * the grep-able `COMPAT(` token and the structured metadata it carries.\n */\nexport function COMPAT(tag: CompatTag): CompatTag {\n return tag;\n}\n\n/**\n * Append-only field helper: declares a schema field that is optional on the wire/disk but always\n * resolves to a concrete default once parsed. This is the sanctioned way to add new fields without\n * breaking older producers that omit them.\n */\nexport function optionalWithDefault<S extends z.ZodTypeAny>(\n schema: S,\n defaultValue: z.infer<S>,\n): z.ZodDefault<z.ZodOptional<S>> {\n return schema.optional().default(defaultValue);\n}\n\n/**\n * Parse `value` with `schema`, returning the parsed value on success or `defaults` on any failure.\n * Used by the file-based stores so corrupt/partial JSON falls back to defaults instead of crashing\n * the daemon (architecture/persistence.md — \"Corrupt/partial JSON → fall back to defaults\").\n */\nexport function safeParseOrDefault<S extends z.ZodTypeAny>(\n schema: S,\n value: unknown,\n defaults: z.infer<S>,\n): z.infer<S> {\n const result = schema.safeParse(value);\n return result.success ? (result.data as z.infer<S>) : defaults;\n}\n\n// ---------------------------------------------------------------------------\n// Shared primitive schemas\n// ---------------------------------------------------------------------------\n\n/**\n * ISO-8601 timestamp string (UTC `Z` or explicit offset), e.g. `2026-06-11T15:00:00.000Z`.\n * Timeline row timestamps are canonical, daemon-owned strings (MAIN-SCOPE.md §9).\n */\nexport const isoTimestampSchema = z.string().datetime({ offset: true });\n\n/** RFC-4122 UUID string (the primary key shape for agents, chat rooms, messages, etc.). */\nexport const uuidSchema = z.string().uuid();\n\n/**\n * base64url character set with no padding (`A–Z a–z 0–9 - _`). The encoding used for stable ids\n * like the daemon `server-id` (`srv_<base64url>`).\n */\nexport const base64UrlSchema = z.string().regex(/^[A-Za-z0-9_-]+$/, \"expected base64url\");\n\n/** Returns true if `value` is a non-empty base64url string. */\nexport function isBase64Url(value: string): boolean {\n return base64UrlSchema.safeParse(value).success;\n}\n\n/**\n * Schema for a prefixed base64url id of the form `${prefix}_<base64url>` (e.g. `srv_AbC-123`).\n */\nexport function prefixedIdSchema(prefix: string): z.ZodString {\n const escaped = prefix.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return z\n .string()\n .regex(new RegExp(`^${escaped}_[A-Za-z0-9_-]+$`), `expected ${prefix}_<base64url> id`);\n}\n","import { z } from \"zod\";\n\nimport { isoTimestampSchema } from \"./validation.js\";\n\n/**\n * WebSocket wire schemas (architecture/websocket-protocol.md).\n *\n * The protocol mixes JSON text frames (these schemas) with a small binary framing for\n * terminal/file streams (see `binary-frames/`). All schemas are append-only: new fields are\n * optional with defaults, types are never narrowed, fields are never removed.\n *\n * Built up across sprint-002:\n * - task-001: top-level envelopes + handshake.\n * - task-003: the session message family union (agent / timeline / permission core).\n */\n\n// ===========================================================================\n// Shared wire primitives\n// ===========================================================================\n\n/**\n * A wire timestamp. Accepts either an epoch-millis number or an ISO-8601 string so the type is\n * never narrowed across versions (append-only rule).\n */\nexport const wireTimestampSchema = z.union([z.number(), isoTimestampSchema]);\nexport type WireTimestamp = z.infer<typeof wireTimestampSchema>;\n\n// ===========================================================================\n// Handshake\n// ===========================================================================\n\n/** Client kinds that may connect. Desktop connects as `browser` (TODO(verify) MAIN-SCOPE). */\nexport const clientTypeSchema = z.enum([\"mobile\", \"browser\", \"cli\", \"mcp\"]);\nexport type ClientType = z.infer<typeof clientTypeSchema>;\n\n/**\n * Client → Server handshake. Must be the first frame on a connection. `capabilities` advertises\n * the `CLIENT_CAPS.*` flags the client supports (a string→boolean map; see client-capabilities.ts).\n */\nexport const helloSchema = z.object({\n type: z.literal(\"hello\"),\n clientId: z.string(),\n clientType: clientTypeSchema,\n protocolVersion: z.number(),\n appVersion: z.string().optional(),\n capabilities: z.record(z.string(), z.boolean()).optional(),\n});\nexport type Hello = z.infer<typeof helloSchema>;\n\n/**\n * The `server_info` payload carried by the `status` envelope. There is no dedicated welcome\n * message — the server emits this after accepting the hello, then begins streaming.\n */\nexport const serverInfoPayloadSchema = z.object({\n status: z.literal(\"server_info\"),\n serverId: z.string(),\n hostname: z.string().optional(),\n version: z.string().optional(),\n capabilities: z.record(z.string(), z.unknown()),\n features: z.record(z.string(), z.unknown()),\n});\nexport type ServerInfoPayload = z.infer<typeof serverInfoPayloadSchema>;\n\n/** Server → Client status envelope wrapping `server_info`. */\nexport const statusSchema = z.object({\n type: z.literal(\"status\"),\n payload: serverInfoPayloadSchema,\n});\nexport type Status = z.infer<typeof statusSchema>;\n\n// ===========================================================================\n// Liveness (JSON ping/pong, NOT RFC6455 ping — browsers/RN cannot access protocol ping)\n// ===========================================================================\n\nexport const pingSchema = z.object({\n type: z.literal(\"ping\"),\n requestId: z.string(),\n clientSentAt: wireTimestampSchema.optional(),\n});\nexport type Ping = z.infer<typeof pingSchema>;\n\nexport const pongSchema = z.object({\n type: z.literal(\"pong\"),\n requestId: z.string(),\n clientSentAt: wireTimestampSchema.optional(),\n serverReceivedAt: wireTimestampSchema,\n serverSentAt: wireTimestampSchema,\n});\nexport type Pong = z.infer<typeof pongSchema>;\n\n// ===========================================================================\n// RPC naming convention\n// ===========================================================================\n\nexport type RpcDirection = \"request\" | \"response\";\n\n/**\n * Builds a dotted RPC name: `domain.provider.operation.direction`\n * (e.g. `checkout.github.set_auto_merge.request`). Segments read left→right:\n * domain → provider/subsystem → operation (a verb) → direction. New RPCs use this form; legacy\n * flat names (e.g. `checkout_pr_merge_request`) remain *accepted* but must not be generated.\n */\nexport function rpcName(\n domain: string,\n providerOrSubsystem: string,\n operation: string,\n direction: RpcDirection,\n): string {\n return `${domain}.${providerOrSubsystem}.${operation}.${direction}`;\n}\n\n// ===========================================================================\n// Agent session config (create_agent_request)\n// ===========================================================================\n\n/** Status the daemon reports for an agent (architecture/persistence.md — `lastStatus`). */\nexport const agentStatusEnum = z.enum([\"initializing\", \"idle\", \"running\", \"error\", \"closed\"]);\nexport type AgentStatus = z.infer<typeof agentStatusEnum>;\n\nexport const imageAttachmentSchema = z\n .object({ mimeType: z.string().optional(), data: z.string().optional() })\n .passthrough();\nexport type ImageAttachment = z.infer<typeof imageAttachmentSchema>;\n\nexport const agentAttachmentsSchema = z\n .object({\n prs: z.array(z.unknown()).optional(),\n issues: z.array(z.unknown()).optional(),\n })\n .passthrough();\n\n/**\n * `AgentSessionConfig` — provider/mode/model and run options for an agent. `provider`/`modeId`/\n * `model`/`thinkingOptionId` are dynamic strings (discovered at runtime per agent-providers.md),\n * not fixed enums, so they are not narrowed here.\n */\nexport const agentSessionConfigSchema = z.object({\n provider: z.string(),\n cwd: z.string(),\n modeId: z.string().optional(),\n model: z.string().optional(),\n /** The model's own underlying LLM provider (e.g. `\"anthropic\"`), pinned alongside `model` when\n * a deferred draft materializes (either the resolved default or an explicit `/model` pick) —\n * required to replay it via `setProviderModel` on first spawn, since neither `createSession`\n * nor `resumeSession` consult this field (Pi resolves its own default at spawn). */\n modelProvider: z.string().optional(),\n thinkingOptionId: z.string().optional(),\n featureValues: z.record(z.string(), z.unknown()).optional(),\n title: z.string().nullable().optional(),\n approvalPolicy: z.string().optional(),\n sandboxMode: z.string().optional(),\n networkAccess: z.boolean().optional(),\n webSearch: z.boolean().optional(),\n extra: z.object({ pi: z.unknown().optional() }).passthrough().optional(),\n systemPrompt: z.string().optional(),\n mcpServers: z.record(z.string(), z.unknown()).optional(),\n});\nexport type AgentSessionConfig = z.infer<typeof agentSessionConfigSchema>;\n\nexport const createAgentRequestSchema = z.object({\n type: z.literal(\"create_agent_request\"),\n requestId: z.string(),\n config: agentSessionConfigSchema,\n env: z.record(z.string(), z.string()).optional(),\n workspaceId: z.string().optional(),\n worktreeName: z.string().optional(),\n initialPrompt: z.string().optional(),\n clientMessageId: z.string().optional(),\n outputSchema: z.record(z.string(), z.unknown()).optional(),\n images: z.array(imageAttachmentSchema).optional(),\n attachments: agentAttachmentsSchema.optional(),\n git: z.unknown().optional(),\n worktree: z.unknown().optional(),\n autoArchive: z.boolean().optional(),\n labels: z.record(z.string(), z.string()).default({}),\n});\nexport type CreateAgentRequest = z.infer<typeof createAgentRequestSchema>;\n\nexport const createAgentResponseSchema = z.object({\n type: z.literal(\"create_agent_response\"),\n requestId: z.string(),\n payload: z.object({ agentId: z.string() }).passthrough(),\n});\nexport type CreateAgentResponse = z.infer<typeof createAgentResponseSchema>;\n\n// ===========================================================================\n// Agent lifecycle messages\n// ===========================================================================\n\nexport const agentUpdateSchema = z.object({\n type: z.literal(\"agent_update\"),\n agentId: z.string(),\n status: agentStatusEnum.optional(),\n title: z.string().nullable().optional(),\n labels: z.record(z.string(), z.string()).optional(),\n});\n\nexport const agentStatusMessageSchema = z.object({\n type: z.literal(\"agent_status\"),\n agentId: z.string(),\n status: agentStatusEnum,\n});\n\nexport const agentListSchema = z.object({\n type: z.literal(\"agent_list\"),\n agents: z.array(z.object({ id: z.string() }).passthrough()),\n});\n\nexport const agentDeletedSchema = z.object({\n type: z.literal(\"agent_deleted\"),\n agentId: z.string(),\n});\n\nexport const agentArchivedSchema = z.object({\n type: z.literal(\"agent_archived\"),\n agentId: z.string(),\n archivedAt: isoTimestampSchema.optional(),\n});\n\n// ===========================================================================\n// Tool-call details + stream events\n// ===========================================================================\n\n/** `ToolCallDetail` — normalized across providers, discriminated on `kind`. */\nexport const toolCallDetailSchema = z.discriminatedUnion(\"kind\", [\n z.object({ kind: z.literal(\"shell\"), command: z.string().optional(), output: z.string().optional() }),\n z.object({ kind: z.literal(\"read\"), path: z.string().optional(), output: z.string().optional() }),\n z.object({\n kind: z.literal(\"edit\"),\n path: z.string().optional(),\n diff: z.string().optional(),\n output: z.string().optional(),\n }),\n z.object({ kind: z.literal(\"write\"), path: z.string().optional(), output: z.string().optional() }),\n z.object({ kind: z.literal(\"search\"), query: z.string().optional(), output: z.string().optional() }),\n z.object({ kind: z.literal(\"fetch\"), url: z.string().optional(), output: z.string().optional() }),\n z.object({\n kind: z.literal(\"task\"),\n description: z.string().optional(),\n output: z.string().optional(),\n }),\n]);\nexport type ToolCallDetail = z.infer<typeof toolCallDetailSchema>;\n\n/** `AgentStreamEvent` — discriminated on `kind`. */\nexport const agentStreamEventSchema = z.discriminatedUnion(\"kind\", [\n z.object({\n kind: z.literal(\"user_message\"),\n messageId: z.string().optional(),\n text: z.string().optional(),\n images: z.array(imageAttachmentSchema).optional(),\n }),\n z.object({\n kind: z.literal(\"assistant_message\"),\n messageId: z.string().optional(),\n text: z.string().optional(),\n }),\n z.object({ kind: z.literal(\"reasoning\"), text: z.string().optional() }),\n z.object({\n kind: z.literal(\"tool_call\"),\n callId: z.string().optional(),\n tool: toolCallDetailSchema,\n status: z.string().optional(),\n }),\n z.object({ kind: z.literal(\"turn_started\"), turnId: z.string().optional() }),\n z.object({ kind: z.literal(\"turn_completed\"), turnId: z.string().optional() }),\n z.object({\n kind: z.literal(\"turn_failed\"),\n turnId: z.string().optional(),\n error: z.string().optional(),\n }),\n z.object({ kind: z.literal(\"turn_canceled\"), turnId: z.string().optional() }),\n z.object({ kind: z.literal(\"error\"), message: z.string().optional() }),\n z.object({\n kind: z.literal(\"queue_update\"),\n steering: z.array(z.string()).optional(),\n followUp: z.array(z.string()).optional(),\n }),\n]);\nexport type AgentStreamEvent = z.infer<typeof agentStreamEventSchema>;\n\nexport const agentStreamSchema = z.object({\n type: z.literal(\"agent_stream\"),\n agentId: z.string(),\n seq: z.number().optional(),\n timestamp: wireTimestampSchema.optional(),\n event: agentStreamEventSchema,\n});\n\n// ===========================================================================\n// Timeline fetch (authoritative paged history)\n// ===========================================================================\n\nexport const timelineDirectionEnum = z.enum([\"before\", \"after\"]);\nexport type TimelineDirection = z.infer<typeof timelineDirectionEnum>;\n\nexport const sourceSeqRangeSchema = z.object({ start: z.number(), end: z.number() });\nexport type SourceSeqRange = z.infer<typeof sourceSeqRangeSchema>;\n\nexport const fetchAgentTimelineRequestSchema = z.object({\n type: z.literal(\"fetch_agent_timeline_request\"),\n requestId: z.string(),\n agentId: z.string(),\n cursor: z.string().nullable().optional(),\n direction: timelineDirectionEnum,\n limit: z.number().optional(),\n});\nexport type FetchAgentTimelineRequest = z.infer<typeof fetchAgentTimelineRequestSchema>;\n\nexport const fetchAgentTimelineResponseSchema = z.object({\n type: z.literal(\"fetch_agent_timeline_response\"),\n requestId: z.string(),\n agentId: z.string().optional(),\n items: z.array(z.unknown()),\n seqStart: z.number(),\n seqEnd: z.number(),\n sourceSeqRanges: z.array(sourceSeqRangeSchema),\n collapsed: z.boolean(),\n hasNewer: z.boolean(),\n startCursor: z.string().nullable().optional(),\n endCursor: z.string().nullable().optional(),\n});\nexport type FetchAgentTimelineResponse = z.infer<typeof fetchAgentTimelineResponseSchema>;\n\n// ===========================================================================\n// Tool-call permissions\n// ===========================================================================\n\nexport const agentPermissionRequestSchema = z.object({\n type: z.literal(\"agent_permission_request\"),\n requestId: z.string(),\n agentId: z.string(),\n toolName: z.string().optional(),\n tool: toolCallDetailSchema.optional(),\n action: z.unknown().optional(),\n responses: z.array(z.string()).optional(),\n});\n\nexport const agentPermissionResolvedSchema = z.object({\n type: z.literal(\"agent_permission_resolved\"),\n requestId: z.string(),\n agentId: z.string(),\n decision: z.string().optional(),\n});\n\n/** respond-to-permission RPC (client → daemon), dotted name `agent.permission.respond.request`. */\nexport const respondToPermissionRequestSchema = z.object({\n type: z.literal(\"agent.permission.respond.request\"),\n requestId: z.string(),\n permissionRequestId: z.string(),\n response: z.unknown(),\n});\n\nexport const respondToPermissionResponseSchema = z.object({\n type: z.literal(\"agent.permission.respond.response\"),\n requestId: z.string(),\n payload: z.object({ resolved: z.boolean() }).passthrough(),\n});\n\n/** Legacy flat name for the same operation — still accepted (parsed), never generated. */\nexport const legacyRespondToPermissionSchema = z.object({\n type: z.literal(\"respond_to_permission\"),\n requestId: z.string(),\n permissionRequestId: z.string(),\n response: z.unknown(),\n});\n\n// ===========================================================================\n// ===========================================================================\n// Rewind RPC (features/rewind.md) — additive, new in sprint-015\n// ===========================================================================\n\nexport const rewindModeSchema = z.enum([\"conversation\", \"files\", \"both\"]);\nexport type RewindMode = z.infer<typeof rewindModeSchema>;\n\nexport const agentRewindRequestSchema = z.object({\n type: z.literal(\"agent.rewind.request\"),\n requestId: z.string(),\n agentId: z.string(),\n messageId: z.string(),\n mode: rewindModeSchema,\n}).passthrough();\nexport type AgentRewindRequest = z.infer<typeof agentRewindRequestSchema>;\n\nexport const agentRewindResponseSchema = z.object({\n type: z.literal(\"agent.rewind.response\"),\n requestId: z.string(),\n payload: z.object({\n agentId: z.string(),\n messageId: z.string(),\n mode: rewindModeSchema,\n truncatedAt: z.string().optional(),\n }).passthrough(),\n}).passthrough();\nexport type AgentRewindResponse = z.infer<typeof agentRewindResponseSchema>;\n\n// ===========================================================================\n// Slash-command operations (sprint-037): Pi built-ins with an RPC equivalent —\n// /session, /compact, /new, /resume, /fork, /clone, /name, /export, /model, /copy.\n// Pi's own RPC docs are explicit that built-in TUI commands without one of these RPC\n// equivalents (e.g. /settings, /hotkeys) are never expanded by the `prompt` command and are\n// intentionally NOT represented on the wire.\n// ===========================================================================\n\nexport const agentTokenUsageSchema = z\n .object({\n input: z.number().optional(),\n output: z.number().optional(),\n cacheRead: z.number().optional(),\n cacheWrite: z.number().optional(),\n total: z.number().optional(),\n })\n .passthrough();\nexport type AgentTokenUsage = z.infer<typeof agentTokenUsageSchema>;\n\nexport const agentContextUsageSchema = z\n .object({\n tokens: z.number().nullable().optional(),\n contextWindow: z.number().optional(),\n percent: z.number().nullable().optional(),\n })\n .passthrough();\nexport type AgentContextUsage = z.infer<typeof agentContextUsageSchema>;\n\n/** `/session` — mirrors Pi RPC `get_session_stats`. */\nexport const agentSessionStatsRequestSchema = z\n .object({\n type: z.literal(\"agent_session_stats_request\"),\n requestId: z.string(),\n agentId: z.string(),\n })\n .passthrough();\nexport type AgentSessionStatsRequest = z.infer<typeof agentSessionStatsRequestSchema>;\n\nexport const agentSessionStatsResponseSchema = z\n .object({\n type: z.literal(\"agent_session_stats_response\"),\n requestId: z.string(),\n payload: z\n .object({\n sessionId: z.string().optional(),\n sessionFile: z.string().optional(),\n userMessages: z.number().optional(),\n assistantMessages: z.number().optional(),\n toolCalls: z.number().optional(),\n toolResults: z.number().optional(),\n totalMessages: z.number().optional(),\n tokens: agentTokenUsageSchema.optional(),\n cost: z.number().optional(),\n contextUsage: agentContextUsageSchema.optional(),\n /** Current model id (sprint-042: poll-authoritative source for the web-client status\n * bar's model segment; back-filled server-side when the provider's own stats omit it). */\n model: z.string().optional(),\n })\n .passthrough(),\n })\n .passthrough();\nexport type AgentSessionStatsResponse = z.infer<typeof agentSessionStatsResponseSchema>;\n\n/** `/compact` — mirrors Pi RPC `compact`. */\nexport const agentCompactRequestSchema = z\n .object({\n type: z.literal(\"agent_compact_request\"),\n requestId: z.string(),\n agentId: z.string(),\n customInstructions: z.string().optional(),\n })\n .passthrough();\nexport type AgentCompactRequest = z.infer<typeof agentCompactRequestSchema>;\n\nexport const agentCompactResponseSchema = z\n .object({\n type: z.literal(\"agent_compact_response\"),\n requestId: z.string(),\n payload: z\n .object({\n summary: z.string().optional(),\n firstKeptEntryId: z.string().optional(),\n tokensBefore: z.number().optional(),\n details: z.unknown().optional(),\n })\n .passthrough(),\n })\n .passthrough();\nexport type AgentCompactResponse = z.infer<typeof agentCompactResponseSchema>;\n\n/** `/new` — mirrors Pi RPC `new_session`. */\nexport const agentNewSessionRequestSchema = z\n .object({\n type: z.literal(\"agent_new_session_request\"),\n requestId: z.string(),\n agentId: z.string(),\n })\n .passthrough();\nexport type AgentNewSessionRequest = z.infer<typeof agentNewSessionRequestSchema>;\n\nexport const agentNewSessionResponseSchema = z\n .object({\n type: z.literal(\"agent_new_session_response\"),\n requestId: z.string(),\n payload: z.object({ cancelled: z.boolean() }).passthrough(),\n })\n .passthrough();\nexport type AgentNewSessionResponse = z.infer<typeof agentNewSessionResponseSchema>;\n\n/** `/resume` — mirrors Pi RPC `switch_session`. */\nexport const agentSwitchSessionRequestSchema = z\n .object({\n type: z.literal(\"agent_switch_session_request\"),\n requestId: z.string(),\n agentId: z.string(),\n sessionPath: z.string(),\n })\n .passthrough();\nexport type AgentSwitchSessionRequest = z.infer<typeof agentSwitchSessionRequestSchema>;\n\nexport const agentSwitchSessionResponseSchema = z\n .object({\n type: z.literal(\"agent_switch_session_response\"),\n requestId: z.string(),\n payload: z.object({ cancelled: z.boolean() }).passthrough(),\n })\n .passthrough();\nexport type AgentSwitchSessionResponse = z.infer<typeof agentSwitchSessionResponseSchema>;\n\n/** `/fork` — mirrors Pi RPC `fork`. */\nexport const agentForkRequestSchema = z\n .object({\n type: z.literal(\"agent_fork_request\"),\n requestId: z.string(),\n agentId: z.string(),\n entryId: z.string(),\n })\n .passthrough();\nexport type AgentForkRequest = z.infer<typeof agentForkRequestSchema>;\n\nexport const agentForkResponseSchema = z\n .object({\n type: z.literal(\"agent_fork_response\"),\n requestId: z.string(),\n payload: z.object({ text: z.string(), cancelled: z.boolean() }).passthrough(),\n })\n .passthrough();\nexport type AgentForkResponse = z.infer<typeof agentForkResponseSchema>;\n\n/** Fork picker — mirrors Pi RPC `get_fork_messages`. */\nexport const agentForkMessagesRequestSchema = z\n .object({\n type: z.literal(\"agent_fork_messages_request\"),\n requestId: z.string(),\n agentId: z.string(),\n })\n .passthrough();\nexport type AgentForkMessagesRequest = z.infer<typeof agentForkMessagesRequestSchema>;\n\nexport const agentForkMessagesResponseSchema = z\n .object({\n type: z.literal(\"agent_fork_messages_response\"),\n requestId: z.string(),\n payload: z\n .object({\n messages: z.array(z.object({ entryId: z.string(), text: z.string() }).passthrough()),\n })\n .passthrough(),\n })\n .passthrough();\nexport type AgentForkMessagesResponse = z.infer<typeof agentForkMessagesResponseSchema>;\n\n/** `/clone` — mirrors Pi RPC `clone`. */\nexport const agentCloneRequestSchema = z\n .object({\n type: z.literal(\"agent_clone_request\"),\n requestId: z.string(),\n agentId: z.string(),\n })\n .passthrough();\nexport type AgentCloneRequest = z.infer<typeof agentCloneRequestSchema>;\n\nexport const agentCloneResponseSchema = z\n .object({\n type: z.literal(\"agent_clone_response\"),\n requestId: z.string(),\n payload: z.object({ cancelled: z.boolean() }).passthrough(),\n })\n .passthrough();\nexport type AgentCloneResponse = z.infer<typeof agentCloneResponseSchema>;\n\n/** `/name` — mirrors Pi RPC `set_session_name`. */\nexport const agentSetSessionNameRequestSchema = z\n .object({\n type: z.literal(\"agent_set_session_name_request\"),\n requestId: z.string(),\n agentId: z.string(),\n name: z.string(),\n })\n .passthrough();\nexport type AgentSetSessionNameRequest = z.infer<typeof agentSetSessionNameRequestSchema>;\n\nexport const agentSetSessionNameResponseSchema = z\n .object({\n type: z.literal(\"agent_set_session_name_response\"),\n requestId: z.string(),\n payload: z.object({}).passthrough().optional(),\n })\n .passthrough();\nexport type AgentSetSessionNameResponse = z.infer<typeof agentSetSessionNameResponseSchema>;\n\n/** `/export` — mirrors Pi RPC `export_html`. */\nexport const agentExportHtmlRequestSchema = z\n .object({\n type: z.literal(\"agent_export_html_request\"),\n requestId: z.string(),\n agentId: z.string(),\n outputPath: z.string().optional(),\n })\n .passthrough();\nexport type AgentExportHtmlRequest = z.infer<typeof agentExportHtmlRequestSchema>;\n\nexport const agentExportHtmlResponseSchema = z\n .object({\n type: z.literal(\"agent_export_html_response\"),\n requestId: z.string(),\n payload: z.object({ path: z.string() }).passthrough(),\n })\n .passthrough();\nexport type AgentExportHtmlResponse = z.infer<typeof agentExportHtmlResponseSchema>;\n\n/** `/model` (set) — mirrors Pi RPC `set_model`. Model payload shape varies by provider. */\nexport const agentSetModelRequestSchema = z\n .object({\n type: z.literal(\"agent_set_model_request\"),\n requestId: z.string(),\n agentId: z.string(),\n provider: z.string(),\n modelId: z.string(),\n })\n .passthrough();\nexport type AgentSetModelRequest = z.infer<typeof agentSetModelRequestSchema>;\n\nexport const agentSetModelResponseSchema = z\n .object({\n type: z.literal(\"agent_set_model_response\"),\n requestId: z.string(),\n payload: z.object({}).passthrough(),\n })\n .passthrough();\nexport type AgentSetModelResponse = z.infer<typeof agentSetModelResponseSchema>;\n\n/** `/model` (cycle) — mirrors Pi RPC `cycle_model`. */\nexport const agentCycleModelRequestSchema = z\n .object({\n type: z.literal(\"agent_cycle_model_request\"),\n requestId: z.string(),\n agentId: z.string(),\n })\n .passthrough();\nexport type AgentCycleModelRequest = z.infer<typeof agentCycleModelRequestSchema>;\n\nexport const agentCycleModelResponseSchema = z\n .object({\n type: z.literal(\"agent_cycle_model_response\"),\n requestId: z.string(),\n payload: z\n .object({\n model: z.unknown().nullable().optional(),\n thinkingLevel: z.string().optional(),\n isScoped: z.boolean().optional(),\n })\n .passthrough(),\n })\n .passthrough();\nexport type AgentCycleModelResponse = z.infer<typeof agentCycleModelResponseSchema>;\n\n/** `/copy` — mirrors Pi RPC `get_last_assistant_text`. */\nexport const agentLastAssistantTextRequestSchema = z\n .object({\n type: z.literal(\"agent_last_assistant_text_request\"),\n requestId: z.string(),\n agentId: z.string(),\n })\n .passthrough();\nexport type AgentLastAssistantTextRequest = z.infer<typeof agentLastAssistantTextRequestSchema>;\n\nexport const agentLastAssistantTextResponseSchema = z\n .object({\n type: z.literal(\"agent_last_assistant_text_response\"),\n requestId: z.string(),\n payload: z.object({ text: z.string().nullable() }).passthrough(),\n })\n .passthrough();\nexport type AgentLastAssistantTextResponse = z.infer<\n typeof agentLastAssistantTextResponseSchema\n>;\n\n// ===========================================================================\n// Steering (steer_agent / follow_up_agent) — inject a message into a LIVE turn\n// (Pi RPC `steer` / `follow_up`, docs/rpc.md). Fire-and-forget like interrupt_agent:\n// does not start a new turn or change agent status. Delivery is confirmed by a\n// `queue_update` stream event, not by the response payload.\n// ===========================================================================\n\nexport const steerAgentRequestSchema = z\n .object({\n type: z.literal(\"steer_agent_request\"),\n requestId: z.string(),\n agentId: z.string(),\n message: z.string(),\n images: z.array(imageAttachmentSchema).optional(),\n clientMessageId: z.string().optional(),\n })\n .passthrough();\nexport type SteerAgentRequest = z.infer<typeof steerAgentRequestSchema>;\n\nexport const steerAgentResponseSchema = z\n .object({\n type: z.literal(\"steer_agent_response\"),\n requestId: z.string(),\n agentId: z.string(),\n ok: z.boolean(),\n })\n .passthrough();\nexport type SteerAgentResponse = z.infer<typeof steerAgentResponseSchema>;\n\nexport const followUpAgentRequestSchema = z\n .object({\n type: z.literal(\"follow_up_agent_request\"),\n requestId: z.string(),\n agentId: z.string(),\n message: z.string(),\n images: z.array(imageAttachmentSchema).optional(),\n clientMessageId: z.string().optional(),\n })\n .passthrough();\nexport type FollowUpAgentRequest = z.infer<typeof followUpAgentRequestSchema>;\n\nexport const followUpAgentResponseSchema = z\n .object({\n type: z.literal(\"follow_up_agent_response\"),\n requestId: z.string(),\n agentId: z.string(),\n ok: z.boolean(),\n })\n .passthrough();\nexport type FollowUpAgentResponse = z.infer<typeof followUpAgentResponseSchema>;\n\n\n// ===========================================================================\n// RPC error\n// ===========================================================================\n\nexport const rpcErrorSchema = z.object({\n type: z.literal(\"rpc_error\"),\n requestId: z.string(),\n code: z.string().optional(),\n message: z.string().optional(),\n error: z.string().optional(),\n});\nexport type RpcError = z.infer<typeof rpcErrorSchema>;\n\n// ===========================================================================\n// Session message union (agent / timeline / permission core)\n// ===========================================================================\n\n/**\n * The discriminated union of session messages defined so far. Later feature sprints extend this\n * union with workspace/git/terminal/chat/schedule/loop families.\n */\nexport const sessionMessageSchema = z.discriminatedUnion(\"type\", [\n createAgentRequestSchema,\n createAgentResponseSchema,\n agentUpdateSchema,\n agentStatusMessageSchema,\n agentListSchema,\n agentDeletedSchema,\n agentArchivedSchema,\n agentStreamSchema,\n fetchAgentTimelineRequestSchema,\n fetchAgentTimelineResponseSchema,\n agentPermissionRequestSchema,\n agentPermissionResolvedSchema,\n respondToPermissionRequestSchema,\n respondToPermissionResponseSchema,\n legacyRespondToPermissionSchema,\n agentRewindRequestSchema,\n agentRewindResponseSchema,\n agentSessionStatsRequestSchema,\n agentSessionStatsResponseSchema,\n agentCompactRequestSchema,\n agentCompactResponseSchema,\n agentNewSessionRequestSchema,\n agentNewSessionResponseSchema,\n agentSwitchSessionRequestSchema,\n agentSwitchSessionResponseSchema,\n agentForkRequestSchema,\n agentForkResponseSchema,\n agentForkMessagesRequestSchema,\n agentForkMessagesResponseSchema,\n agentCloneRequestSchema,\n agentCloneResponseSchema,\n agentSetSessionNameRequestSchema,\n agentSetSessionNameResponseSchema,\n agentExportHtmlRequestSchema,\n agentExportHtmlResponseSchema,\n agentSetModelRequestSchema,\n agentSetModelResponseSchema,\n agentCycleModelRequestSchema,\n agentCycleModelResponseSchema,\n agentLastAssistantTextRequestSchema,\n agentLastAssistantTextResponseSchema,\n steerAgentRequestSchema,\n steerAgentResponseSchema,\n followUpAgentRequestSchema,\n followUpAgentResponseSchema,\n rpcErrorSchema,\n]);\nexport type SessionMessage = z.infer<typeof sessionMessageSchema>;\n\n/**\n * Structural fallback for any session message (carries a `type` discriminant). Unknown/future\n * session types still parse here (append-only / \"ignore unknown type per handler policy\"), so the\n * envelope accepts them and handlers validate per-family with the specific schema above.\n */\nexport const sessionMessageBaseSchema = z.object({ type: z.string() }).passthrough();\nexport type SessionMessageBase = z.infer<typeof sessionMessageBaseSchema>;\n\n// ===========================================================================\n// Session envelope + top-level union\n// ===========================================================================\n\n/**\n * Envelope wrapping the rich session message union. Known message types validate strictly against\n * `sessionMessageSchema`; unknown/future types fall back to the structural base so an older client\n * still parses newer messages.\n */\nexport const sessionEnvelopeSchema = z.object({\n type: z.literal(\"session\"),\n message: z.union([sessionMessageSchema, sessionMessageBaseSchema]),\n});\nexport type SessionEnvelope = z.infer<typeof sessionEnvelopeSchema>;\n\n/** Discriminated union of all top-level envelopes, keyed by `type`. */\nexport const topLevelEnvelopeSchema = z.discriminatedUnion(\"type\", [\n helloSchema,\n statusSchema,\n pingSchema,\n pongSchema,\n sessionEnvelopeSchema,\n]);\nexport type TopLevelEnvelope = z.infer<typeof topLevelEnvelopeSchema>;\n","import { z } from \"zod\";\n\n/**\n * Terminal stream binary frame codec (architecture/websocket-protocol.md § Binary frames,\n * features/terminals.md § Binary stream protocol).\n *\n * Frame layout: `[1-byte opcode][1-byte slot][payload]`.\n * - `slot` demuxes multiple terminals on one socket (0–255).\n * - Output/Input/Snapshot/Restore payloads are raw bytes (pass-through).\n * - Resize payload is UTF-8 JSON `{ rows, cols }`.\n *\n * Uses `Uint8Array` (not Node `Buffer`) so the codec runs in browser/RN as well as Node.\n */\n\n/** Terminal frame opcodes. `Restore` value is TODO(verify) against the live codec. */\nexport const TerminalOpcode = {\n Output: 0x01,\n Input: 0x02,\n Resize: 0x03,\n Snapshot: 0x04,\n Restore: 0x05,\n} as const;\n\nexport type TerminalOpcodeName = keyof typeof TerminalOpcode;\nexport type TerminalOpcodeValue = (typeof TerminalOpcode)[TerminalOpcodeName];\n\nconst OPCODE_BY_VALUE: Record<number, TerminalOpcodeName> = Object.fromEntries(\n Object.entries(TerminalOpcode).map(([name, value]) => [value, name as TerminalOpcodeName]),\n);\n\n/** Resize payload schema (`{ rows, cols }`, non-negative integers). */\nexport const terminalResizeSchema = z.object({\n rows: z.number().int().nonnegative(),\n cols: z.number().int().nonnegative(),\n});\nexport type TerminalResize = z.infer<typeof terminalResizeSchema>;\n\nexport type TerminalFrame =\n | { opcode: \"Output\"; slot: number; data: Uint8Array }\n | { opcode: \"Input\"; slot: number; data: Uint8Array }\n | { opcode: \"Resize\"; slot: number; rows: number; cols: number }\n | { opcode: \"Snapshot\"; slot: number; data: Uint8Array }\n | { opcode: \"Restore\"; slot: number; data: Uint8Array };\n\n/** Thrown when a frame cannot be decoded (too short, unknown opcode, bad payload). */\nexport class TerminalFrameError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TerminalFrameError\";\n }\n}\n\nconst textEncoder = new TextEncoder();\nconst textDecoder = new TextDecoder();\n\nfunction assertSlot(slot: number): void {\n if (!Number.isInteger(slot) || slot < 0 || slot > 0xff) {\n throw new TerminalFrameError(`slot must be an integer 0–255, got ${slot}`);\n }\n}\n\nfunction frameWithPayload(opcodeValue: number, slot: number, payload: Uint8Array): Uint8Array {\n const out = new Uint8Array(2 + payload.length);\n out[0] = opcodeValue;\n out[1] = slot;\n out.set(payload, 2);\n return out;\n}\n\n/** Encode a typed terminal frame to bytes. */\nexport function encodeTerminalFrame(frame: TerminalFrame): Uint8Array {\n assertSlot(frame.slot);\n switch (frame.opcode) {\n case \"Output\":\n return frameWithPayload(TerminalOpcode.Output, frame.slot, frame.data);\n case \"Input\":\n return frameWithPayload(TerminalOpcode.Input, frame.slot, frame.data);\n case \"Snapshot\":\n return frameWithPayload(TerminalOpcode.Snapshot, frame.slot, frame.data);\n case \"Restore\":\n return frameWithPayload(TerminalOpcode.Restore, frame.slot, frame.data);\n case \"Resize\": {\n const json = JSON.stringify(\n terminalResizeSchema.parse({ rows: frame.rows, cols: frame.cols }),\n );\n return frameWithPayload(TerminalOpcode.Resize, frame.slot, textEncoder.encode(json));\n }\n }\n}\n\n/** Decode bytes into a typed terminal frame. Throws `TerminalFrameError` on invalid input. */\nexport function decodeTerminalFrame(bytes: Uint8Array): TerminalFrame {\n if (bytes.length < 2) {\n throw new TerminalFrameError(`frame too short: ${bytes.length} bytes (need ≥ 2)`);\n }\n const opcodeValue = bytes[0] as number;\n const slot = bytes[1] as number;\n const opcode = OPCODE_BY_VALUE[opcodeValue];\n if (!opcode) {\n throw new TerminalFrameError(`unknown terminal opcode 0x${opcodeValue.toString(16)}`);\n }\n const payload = bytes.subarray(2);\n\n if (opcode === \"Resize\") {\n let parsed: unknown;\n try {\n parsed = JSON.parse(textDecoder.decode(payload));\n } catch {\n throw new TerminalFrameError(\"resize payload is not valid JSON\");\n }\n const result = terminalResizeSchema.safeParse(parsed);\n if (!result.success) {\n throw new TerminalFrameError(\"resize payload is not { rows, cols }\");\n }\n return { opcode: \"Resize\", slot, rows: result.data.rows, cols: result.data.cols };\n }\n\n // Output/Input/Snapshot/Restore: raw bytes pass through (copied so callers own the buffer).\n return { opcode, slot, data: payload.slice() };\n}\n\n/** Returns the decoded frame, or `null` instead of throwing (safe handling of unknown opcodes). */\nexport function tryDecodeTerminalFrame(bytes: Uint8Array): TerminalFrame | null {\n try {\n return decodeTerminalFrame(bytes);\n } catch {\n return null;\n }\n}\n","import { z } from \"zod\";\n\n/**\n * File-transfer binary frame codec (architecture/websocket-protocol.md § Binary frames,\n * features/file-explorer-transfer.md § Binary transfer frames). Separate from the terminal codec\n * but lives in the same module family.\n *\n * Frame layout: `[1-byte opcode][1-byte stream][payload]`.\n * - `stream` multiplexes concurrent transfers on one socket (0–255).\n * - A transfer is `Begin` (metadata header) → N × `Chunk` (raw bytes) → `End` (completion marker),\n * or `Error` to abort.\n *\n * NOTE: exact opcode values / chunk sizing / completion marker are TODO(verify) against the live\n * codec; this is a faithful chunked-with-completion-marker reconstruction from the scope.\n */\n\nexport const FileTransferOpcode = {\n Begin: 0x10,\n Chunk: 0x11,\n End: 0x12,\n Error: 0x13,\n} as const;\n\nexport type FileTransferOpcodeName = keyof typeof FileTransferOpcode;\n\nconst OPCODE_BY_VALUE: Record<number, FileTransferOpcodeName> = Object.fromEntries(\n Object.entries(FileTransferOpcode).map(([name, value]) => [\n value,\n name as FileTransferOpcodeName,\n ]),\n);\n\n/** Metadata header for a transfer (carried by the `Begin` frame as UTF-8 JSON). */\nexport const fileTransferBeginSchema = z.object({\n transferId: z.string(),\n fileName: z.string().optional(),\n mimeType: z.string().optional(),\n totalBytes: z.number().int().nonnegative().optional(),\n});\nexport type FileTransferBegin = z.infer<typeof fileTransferBeginSchema>;\n\nexport type FileTransferFrame =\n | { opcode: \"Begin\"; stream: number; meta: FileTransferBegin }\n | { opcode: \"Chunk\"; stream: number; data: Uint8Array }\n | { opcode: \"End\"; stream: number; ok: boolean }\n | { opcode: \"Error\"; stream: number; message: string };\n\nexport class FileTransferFrameError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"FileTransferFrameError\";\n }\n}\n\nconst textEncoder = new TextEncoder();\nconst textDecoder = new TextDecoder();\n\nfunction assertStream(stream: number): void {\n if (!Number.isInteger(stream) || stream < 0 || stream > 0xff) {\n throw new FileTransferFrameError(`stream must be an integer 0–255, got ${stream}`);\n }\n}\n\nfunction frame(opcodeValue: number, stream: number, payload: Uint8Array): Uint8Array {\n const out = new Uint8Array(2 + payload.length);\n out[0] = opcodeValue;\n out[1] = stream;\n out.set(payload, 2);\n return out;\n}\n\nexport function encodeFileTransferFrame(f: FileTransferFrame): Uint8Array {\n assertStream(f.stream);\n switch (f.opcode) {\n case \"Begin\":\n return frame(\n FileTransferOpcode.Begin,\n f.stream,\n textEncoder.encode(JSON.stringify(fileTransferBeginSchema.parse(f.meta))),\n );\n case \"Chunk\":\n return frame(FileTransferOpcode.Chunk, f.stream, f.data);\n case \"End\":\n // Completion marker; payload encodes the ok/abort flag as a single byte.\n return frame(FileTransferOpcode.End, f.stream, new Uint8Array([f.ok ? 1 : 0]));\n case \"Error\":\n return frame(FileTransferOpcode.Error, f.stream, textEncoder.encode(f.message));\n }\n}\n\nexport function decodeFileTransferFrame(bytes: Uint8Array): FileTransferFrame {\n if (bytes.length < 2) {\n throw new FileTransferFrameError(`frame too short: ${bytes.length} bytes (need ≥ 2)`);\n }\n const opcodeValue = bytes[0] as number;\n const stream = bytes[1] as number;\n const opcode = OPCODE_BY_VALUE[opcodeValue];\n if (!opcode) {\n throw new FileTransferFrameError(`unknown file-transfer opcode 0x${opcodeValue.toString(16)}`);\n }\n const payload = bytes.subarray(2);\n\n switch (opcode) {\n case \"Begin\": {\n let parsed: unknown;\n try {\n parsed = JSON.parse(textDecoder.decode(payload));\n } catch {\n throw new FileTransferFrameError(\"begin payload is not valid JSON\");\n }\n const result = fileTransferBeginSchema.safeParse(parsed);\n if (!result.success) {\n throw new FileTransferFrameError(\"begin payload is not a valid transfer header\");\n }\n return { opcode: \"Begin\", stream, meta: result.data };\n }\n case \"Chunk\":\n return { opcode: \"Chunk\", stream, data: payload.slice() };\n case \"End\":\n return { opcode: \"End\", stream, ok: (payload[0] ?? 1) !== 0 };\n case \"Error\":\n return { opcode: \"Error\", stream, message: textDecoder.decode(payload) };\n }\n}\n\nexport function tryDecodeFileTransferFrame(bytes: Uint8Array): FileTransferFrame | null {\n try {\n return decodeFileTransferFrame(bytes);\n } catch {\n return null;\n }\n}\n","import { z } from \"zod\";\n\n/**\n * Provider manifest types (features/agent-providers.md § Registration surface, § Capability flags).\n *\n * The manifest is UI scaffolding only — actual models/modes/features are discovered at runtime via\n * the provider RPC. These types express provider definitions, mode metadata (icon + colorTier), and\n * the `AgentCapabilityFlags` shape.\n */\n\n/** Provider ids must match `/^[a-z][a-z0-9-]*$/` (agent-providers.md). */\nexport const providerIdSchema = z.string().regex(/^[a-z][a-z0-9-]*$/, \"invalid provider id\");\n\n/** Mode danger/intent tier driving UI color. */\nexport const colorTierSchema = z.enum([\"safe\", \"moderate\", \"dangerous\", \"planning\"]);\nexport type ColorTier = z.infer<typeof colorTierSchema>;\n\n/** Per-provider capability flags. */\nexport const agentCapabilityFlagsSchema = z.object({\n supportsStreaming: z.boolean(),\n supportsSessionPersistence: z.boolean(),\n supportsDynamicModes: z.boolean(),\n supportsMcpServers: z.boolean(),\n supportsReasoningStream: z.boolean(),\n supportsToolInvocations: z.boolean(),\n // Rewind capability flags (features/rewind.md) — additive, optional, old clients ignore.\n supportsRewindConversation: z.boolean().optional(),\n supportsRewindFiles: z.boolean().optional(),\n supportsRewindBoth: z.boolean().optional(),\n // Steering capability (features/agent-sessions.md § Steering) — additive, optional. When true,\n // the provider accepts `steer`/`follow_up` messages injected into a live turn.\n supportsSteering: z.boolean().optional(),\n}).passthrough();\nexport type AgentCapabilityFlags = z.infer<typeof agentCapabilityFlagsSchema>;\n\n/** UI metadata for a provider mode (plan/default/full-access/...). */\nexport const providerModeSchema = z.object({\n id: z.string(),\n label: z.string().optional(),\n description: z.string().optional(),\n icon: z.string().optional(),\n colorTier: colorTierSchema,\n});\nexport type ProviderMode = z.infer<typeof providerModeSchema>;\n\n/** A provider manifest definition. `extends` marks a custom Pi-compatible profile. */\nexport const providerDefinitionSchema = z.object({\n id: providerIdSchema,\n label: z.string(),\n description: z.string().optional(),\n extends: z.string().optional(),\n modes: z.array(providerModeSchema),\n capabilities: agentCapabilityFlagsSchema,\n});\nexport type ProviderDefinition = z.infer<typeof providerDefinitionSchema>;\n","/**\n * Pluggable transport abstraction — direct WebSocket and (future) relay E2EE share this API.\n * See architecture/client-app-runtime.md § Layered client library.\n */\n\nexport type TransportMessageHandler = (data: string | ArrayBuffer | Blob) => void;\nexport type TransportCloseHandler = (code: number, reason: string) => void;\nexport type TransportErrorHandler = (error: unknown) => void;\n\nexport interface Transport {\n /** Initiate the connection. Resolves when the socket is open (raw, pre-handshake). */\n connect(url: string): Promise<void>;\n /** Send a text frame. */\n sendText(data: string): void;\n /** Send a binary frame. */\n sendBinary(data: Uint8Array): void;\n /** Close the transport cleanly. */\n close(code?: number, reason?: string): void;\n /** Whether the transport is currently open and writeable. */\n readonly isOpen: boolean;\n\n onMessage: TransportMessageHandler | null;\n onClose: TransportCloseHandler | null;\n onError: TransportErrorHandler | null;\n}\n\n// ─── Browser / Node WebSocket transport ────────────────────────────────────────\n\nexport type AnyWebSocket = {\n readyState: number;\n send(data: string | ArrayBuffer | Uint8Array): void;\n close(code?: number, reason?: string): void;\n onopen: ((ev: unknown) => void) | null;\n onclose: ((ev: { code: number; reason: string }) => void) | null;\n onerror: ((ev: unknown) => void) | null;\n onmessage: ((ev: { data: unknown }) => void) | null;\n};\n\nexport type WsFactory = (url: string, protocols?: string[]) => AnyWebSocket;\n\n// Node `ws` compatibility: ws closes have reason as Buffer, not string.\nexport function reasonString(reason: unknown): string {\n if (typeof reason === \"string\") return reason;\n if (reason instanceof Uint8Array || Buffer.isBuffer(reason)) return reason.toString(\"utf8\");\n return String(reason ?? \"\");\n}\n\n/**\n * Create a direct WebSocket transport using the supplied factory.\n * Default factory uses the global `WebSocket` (browser/RN/Node 22+) or throws.\n */\nexport function createWebSocketTransport(factory?: WsFactory): Transport {\n let ws: AnyWebSocket | null = null;\n const self: Transport = {\n onMessage: null,\n onClose: null,\n onError: null,\n get isOpen(): boolean {\n return ws !== null && ws.readyState === 1; // OPEN\n },\n connect(url: string): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const create = factory ?? (() => new WebSocket(url) as unknown as AnyWebSocket);\n try {\n ws = factory ? create(url) : (new WebSocket(url) as unknown as AnyWebSocket);\n } catch (err) {\n reject(err);\n return;\n }\n ws.onopen = () => resolve();\n ws.onerror = (ev) => {\n if (ws?.readyState !== 1) {\n reject(ev);\n }\n self.onError?.(ev);\n };\n ws.onclose = (ev) => {\n self.onClose?.(ev.code, reasonString(ev.reason));\n };\n ws.onmessage = (ev) => {\n const { data } = ev;\n if (data instanceof ArrayBuffer || data instanceof Uint8Array) {\n self.onMessage?.(data as ArrayBuffer);\n } else if (typeof data === \"string\") {\n self.onMessage?.(data);\n } else if (typeof Blob !== \"undefined\" && data instanceof Blob) {\n // Browser default `binaryType` is \"blob\" — DaemonClient.handleIncoming already\n // reads Blob payloads asynchronously via `.arrayBuffer()`, so forward it as-is\n // instead of silently dropping every binary (terminal/file-transfer) frame.\n self.onMessage?.(data);\n } else if (Buffer.isBuffer(data)) {\n // Node `ws` may deliver Buffer\n self.onMessage?.(\n data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer,\n );\n }\n };\n });\n },\n sendText(data: string): void {\n ws?.send(data);\n },\n sendBinary(data: Uint8Array): void {\n ws?.send(data);\n },\n close(code = 1000, reason = \"\"): void {\n ws?.close(code, reason);\n ws = null;\n },\n };\n return self;\n}\n","/**\n * Pure-JS base64 codec (no Node `Buffer`) so the relay's crypto framing runs identically in the\n * daemon (Node) and future browser/RN clients — see architecture/relay-e2ee.md § Behavior\n * (per-message wire format uses `base64([nonce] ++ ciphertext)`).\n */\nconst CHARS = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n/** Encode raw bytes as standard (padded) base64. */\nexport function encodeBase64(bytes) {\n let out = \"\";\n for (let i = 0; i < bytes.length; i += 3) {\n const b0 = bytes[i];\n const hasB1 = i + 1 < bytes.length;\n const hasB2 = i + 2 < bytes.length;\n const b1 = hasB1 ? bytes[i + 1] : 0;\n const b2 = hasB2 ? bytes[i + 2] : 0;\n out += CHARS[b0 >> 2];\n out += CHARS[((b0 & 0x03) << 4) | (b1 >> 4)];\n out += hasB1 ? CHARS[((b1 & 0x0f) << 2) | (b2 >> 6)] : \"=\";\n out += hasB2 ? CHARS[b2 & 0x3f] : \"=\";\n }\n return out;\n}\n/** Decode standard base64 (padded or not) into raw bytes. Throws on invalid characters. */\nexport function decodeBase64(input) {\n const clean = input.replace(/=+$/, \"\");\n const bytes = [];\n let buffer = 0;\n let bits = 0;\n for (const ch of clean) {\n const val = CHARS.indexOf(ch);\n if (val === -1)\n throw new Error(`invalid base64 character: ${JSON.stringify(ch)}`);\n buffer = (buffer << 6) | val;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n bytes.push((buffer >> bits) & 0xff);\n }\n }\n return new Uint8Array(bytes);\n}\n//# sourceMappingURL=base64.js.map","/**\n * Relay encrypted channel primitives (architecture/relay-e2ee.md § Channel API, § Handshake\n * frames, § Behavior). `createClientChannel` and `createDaemonChannel` expose an IDENTICAL API so\n * the daemon and client share the same E2EE code path over a relay-provided transport.\n *\n * Handshake:\n * client → daemon: e2ee_hello { ephemeralPublicKey } (client's FRESH ephemeral Curve25519 key)\n * daemon → client: e2ee_ready (handshake acknowledgement)\n * both: sharedKey = ECDH(localSecret, remotePublic) (Curve25519 box.before)\n *\n * Per-message wire format (after handshake):\n * frame = base64( [24-byte random nonce] ++ box(plaintext, nonce, sharedKey) ) // XSalsa20-Poly1305\n * receive → split nonce/ciphertext → box_open → reject (drop, never throw to the app) on auth failure.\n *\n * The daemon secret never leaves the daemon; the client keypair is ephemeral per connection, so\n * there is no persistent client-side secret to steal. Session keys are fresh per session, so\n * ciphertext cannot replay across sessions — but replay protection WITHIN a live session is not\n * implemented (random nonces, no counter) per architecture/relay-e2ee.md § Behavior TODO(verify).\n */\nimport nacl from \"tweetnacl\";\nimport { decodeBase64, encodeBase64 } from \"./base64.js\";\nfunction parseFrame(data) {\n try {\n const parsed = JSON.parse(data);\n if (!parsed || typeof parsed.type !== \"string\")\n return null;\n return parsed;\n }\n catch {\n return null;\n }\n}\nconst NONCE_LENGTH = 24; // XSalsa20-Poly1305 nonce size (nacl.box.nonceLength)\n/** Encrypt raw `message` bytes under `sharedKey`, producing the wire frame `base64(nonce ++ ciphertext)`. */\nfunction sealBytes(message, sharedKey) {\n const nonce = nacl.randomBytes(NONCE_LENGTH);\n const ciphertext = nacl.box.after(message, nonce, sharedKey);\n const combined = new Uint8Array(nonce.length + ciphertext.length);\n combined.set(nonce, 0);\n combined.set(ciphertext, nonce.length);\n return encodeBase64(combined);\n}\n/** Decrypt a `base64(nonce ++ ciphertext)` wire frame under `sharedKey` to raw bytes, or `null` on auth failure. */\nfunction openBytes(frame, sharedKey) {\n let combined;\n try {\n combined = decodeBase64(frame);\n }\n catch {\n return null;\n }\n if (combined.length < NONCE_LENGTH)\n return null;\n const nonce = combined.slice(0, NONCE_LENGTH);\n const ciphertext = combined.slice(NONCE_LENGTH);\n return nacl.box.open.after(ciphertext, nonce, sharedKey);\n}\n/** Encrypt `plaintext` under `sharedKey`, producing the wire frame `base64(nonce ++ ciphertext)`. */\nfunction seal(plaintext, sharedKey) {\n return sealBytes(new TextEncoder().encode(plaintext), sharedKey);\n}\n/** Decrypt a `base64(nonce ++ ciphertext)` wire frame under `sharedKey`, or return `null` on auth failure. */\nfunction open(frame, sharedKey) {\n const opened = openBytes(frame, sharedKey);\n return opened ? new TextDecoder().decode(opened) : null;\n}\n/** Shared app-frame plumbing once both sides agree on `sharedKey` — identical for client/daemon. */\nfunction wireAppMessages(role, attachment, transport, events, getSharedKey, isReady) {\n let closed = false;\n transport.onClose((reason) => {\n closed = true;\n events.onClose?.(reason);\n });\n return {\n handleAppFrame(frame) {\n if (closed)\n return;\n const sharedKey = getSharedKey();\n if (!isReady() || !sharedKey)\n return; // refuse ANY app message before handshake completes\n const plaintext = open(frame.frame, sharedKey);\n if (plaintext === null) {\n events.onAuthError?.(new Error(`relay ${role} channel: message failed authentication`));\n return;\n }\n events.onMessage?.(plaintext);\n },\n handleBinaryFrame(frame) {\n if (closed)\n return;\n const sharedKey = getSharedKey();\n if (!isReady() || !sharedKey)\n return; // refuse ANY app message before handshake completes\n const bytes = openBytes(frame.frame, sharedKey);\n if (bytes === null) {\n events.onAuthError?.(new Error(`relay ${role} channel: binary message failed authentication`));\n return;\n }\n events.onBinaryMessage?.(bytes);\n },\n send(plaintext) {\n if (closed)\n throw new Error(`relay ${role} channel: cannot send after close()`);\n const sharedKey = getSharedKey();\n if (!isReady() || !sharedKey) {\n throw new Error(`relay ${role} channel: cannot send before the E2EE handshake completes`);\n }\n transport.send(JSON.stringify({ type: \"e2ee_app\", frame: seal(plaintext, sharedKey) }));\n },\n sendBinary(bytes) {\n if (closed)\n throw new Error(`relay ${role} channel: cannot send after close()`);\n const sharedKey = getSharedKey();\n if (!isReady() || !sharedKey) {\n throw new Error(`relay ${role} channel: cannot send before the E2EE handshake completes`);\n }\n transport.send(JSON.stringify({ type: \"e2ee_bin\", frame: sealBytes(bytes, sharedKey) }));\n },\n close() {\n if (closed)\n return;\n closed = true;\n transport.close();\n },\n };\n}\n/**\n * Daemon side of an encrypted relay channel. The daemon waits for the client's `e2ee_hello`,\n * derives the shared key from its own persistent secret key + the client's fresh ephemeral public\n * key, replies with `e2ee_ready`, and only then becomes `ready` (refusing all app traffic before\n * that — architecture/relay-e2ee.md § Error Handling).\n */\nexport function createDaemonChannel(opts) {\n const { transport, attachment, daemonKeypair } = opts;\n const events = opts.events ?? {};\n let sharedKey = null;\n let ready = false;\n const plumbing = wireAppMessages(\"daemon\", attachment, transport, events, () => sharedKey, () => ready);\n transport.onMessage((data) => {\n const frame = parseFrame(data);\n if (!frame)\n return; // ignore anything the relay/peer sends that isn't a recognized frame\n if (frame.type === \"e2ee_hello\") {\n // Re-run the handshake on EVERY e2ee_hello rather than ignoring it once `ready`. The relay\n // multiplexes one deterministic session id across however many client sockets attach over\n // the daemon's lifetime (browser reload, second tab, reconnect) — each is a legitimate NEW\n // peer presenting a fresh ephemeral key, not a replay of the first one. Re-deriving simply\n // re-arms the channel for whichever peer is currently attached (\"last hello wins\"); a truly\n // identical replayed frame just re-derives the same shared key, so this is idempotent for\n // an actual replay and correct for a new peer.\n let remotePublicKey;\n try {\n remotePublicKey = decodeBase64(frame.ephemeralPublicKey);\n }\n catch {\n return;\n }\n sharedKey = nacl.box.before(remotePublicKey, daemonKeypair.secretKey);\n ready = true;\n transport.send(JSON.stringify({ type: \"e2ee_ready\" }));\n events.onReady?.();\n return;\n }\n if (frame.type === \"e2ee_app\") {\n plumbing.handleAppFrame(frame);\n return;\n }\n if (frame.type === \"e2ee_bin\") {\n plumbing.handleBinaryFrame(frame);\n }\n // `e2ee_ready` is never sent to a daemon channel; ignore if it arrives.\n });\n return {\n role: \"daemon\",\n attachment,\n get ready() {\n return ready;\n },\n send: plumbing.send,\n sendBinary: plumbing.sendBinary,\n close: plumbing.close,\n };\n}\n/**\n * Client side of an encrypted relay channel. The client already knows the daemon's persistent\n * public key (from the pairing offer), so it generates a FRESH ephemeral keypair, derives the\n * shared key immediately, and sends `e2ee_hello`. It becomes `ready` only after the daemon\n * confirms with `e2ee_ready` — mirroring the daemon's \"no app traffic before handshake\" gating on\n * the client side too, for a genuinely symmetric API.\n */\nexport function createClientChannel(opts) {\n const { transport, attachment, daemonPublicKey } = opts;\n const events = opts.events ?? {};\n const ephemeral = nacl.box.keyPair();\n const sharedKey = nacl.box.before(daemonPublicKey, ephemeral.secretKey);\n let ready = false;\n const plumbing = wireAppMessages(\"client\", attachment, transport, events, () => sharedKey, () => ready);\n transport.onMessage((data) => {\n const frame = parseFrame(data);\n if (!frame)\n return;\n if (frame.type === \"e2ee_ready\") {\n if (ready)\n return;\n ready = true;\n events.onReady?.();\n return;\n }\n if (frame.type === \"e2ee_app\") {\n plumbing.handleAppFrame(frame);\n return;\n }\n if (frame.type === \"e2ee_bin\") {\n plumbing.handleBinaryFrame(frame);\n }\n // `e2ee_hello` is never sent to a client channel; ignore if it arrives.\n });\n transport.send(JSON.stringify({\n type: \"e2ee_hello\",\n ephemeralPublicKey: encodeBase64(ephemeral.publicKey),\n }));\n return {\n role: \"client\",\n attachment,\n get ready() {\n return ready;\n },\n send: plumbing.send,\n sendBinary: plumbing.sendBinary,\n close: plumbing.close,\n };\n}\n//# sourceMappingURL=channel.js.map","/**\n * Deterministic relay session id derivation (architecture/relay-e2ee.md § Pairing).\n *\n * The relay's rendezvous `sessionId` (the `{ type: \"relay_register\", sessionId }` frame) must be\n * something both sides can compute independently from data the pairing link already carries — the\n * daemon's persistent public key — so a pairing link keeps working across relay reconnects\n * (each of which previously minted a fresh random session id, silently invalidating any link\n * printed before the next drop).\n *\n * This derivation adds no new secret: the pairing link already carries the daemon's public key\n * and is documented as \"the trust anchor, treat it like a password\" (relay-e2ee.md § Pairing). A\n * session id computed from that same public value reveals nothing an attacker didn't already have\n * once they held the link. What it changes is durability — the daemon and any pairing-link holder\n * now agree on the same session id forever (until the keypair itself is regenerated), instead of a\n * fresh id being renegotiated implicitly on every reconnect.\n */\nimport nacl from \"tweetnacl\";\nconst SESSION_ID_BYTES = 16; // 32 hex chars — plenty of entropy for a rendezvous label, not a secret.\n/** Derive the relay session id for a daemon identified by `publicKey` (its persistent Curve25519 public key). */\nexport function deriveRelaySessionId(publicKey) {\n const digest = nacl.hash(publicKey); // SHA-512, 64 bytes\n let hex = \"\";\n for (let i = 0; i < SESSION_ID_BYTES; i++) {\n hex += digest[i].toString(16).padStart(2, \"0\");\n }\n return hex;\n}\n//# sourceMappingURL=session-id.js.map","/**\n * Client-side relay E2EE transport (architecture/relay-e2ee.md § Pairing, § Behavior;\n * architecture/client-app-runtime.md § Layered client library — relay transport).\n *\n * `createRelayTransport(...)` implements the same {@link Transport} interface as\n * `createWebSocketTransport` (`./transport.js`), so `DaemonClient` can use either interchangeably —\n * \"relay rides the same API\" (see `index.ts`). Internally it connects to the relay endpoint,\n * registers under `sessionId` (matching the daemon's outbound registration in\n * `packages/server/src/daemon/relay-transport.ts`), generates a FRESH ephemeral Curve25519 keypair,\n * completes the `e2ee_hello`/`e2ee_ready` handshake via `createClientChannel`\n * (`@av-pi-studio/relay`), and only then reports the transport as open — no app RPC (including\n * `hello`) can cross the wire before the handshake finishes.\n *\n * `sendBinary()` works too (terminal I/O, file-transfer chunks) — carried as the channel's\n * `e2ee_bin` sibling wire frame to `e2ee_app` (`@av-pi-studio/relay`'s `channel.ts`). It's still a\n * JSON text frame under the hood (base64-wrapped ciphertext), never a raw binary WS frame, so the\n * relay bridge itself needs no changes — only the channel layer and this transport's `onmessage`\n * dispatch (`onBinaryMessage` → `self.onMessage` with an `ArrayBuffer`, mirroring\n * `createWebSocketTransport`'s binary delivery contract) know binary exists.\n *\n * TODO(verify): the relay's registration frame format matches\n * `packages/server/src/daemon/relay-transport.ts`'s choice (`{ type: \"relay_register\", sessionId\n * }`) — the real protocol is unresolved upstream (architecture/relay-e2ee.md § TODO(verify)).\n */\nimport { createClientChannel, deriveRelaySessionId, type EncryptedChannel } from \"@av-pi-studio/relay\";\n\nimport type { Transport } from \"./transport.js\";\nimport { reasonString, type AnyWebSocket, type WsFactory } from \"./transport.js\";\n\nexport interface RelayTransportOptions {\n /**\n * The relay rendezvous session id. Optional — defaults to\n * `deriveRelaySessionId(daemonPublicKey)`, the SAME deterministic id the daemon's own outbound\n * dial registers under (`packages/server/src/daemon/relay-transport.ts`), so a pairing offer's\n * public key alone is enough to find the daemon on the relay without a separately-transmitted\n * session id.\n */\n sessionId?: string;\n /** The daemon's persistent Curve25519 public key, from the pairing offer — never a fresh key. */\n daemonPublicKey: Uint8Array;\n /** Inject a WebSocket factory (tests). Defaults to the global `WebSocket`. */\n factory?: WsFactory;\n}\n\n/** Build the relay's own `ws://`/`wss://` dial URL from a pairing offer's `relay` info. */\nexport function relayDialUrl(relay: { endpoint: string; useTls: boolean }): string {\n const scheme = relay.useTls ? \"wss\" : \"ws\";\n // Accept `http(s)://`/`ws(s)://`-prefixed endpoints (someone pasted a URL, not a bare host)\n // and normalize to the scheme `useTls` actually calls for — never concatenate two schemes.\n const host = relay.endpoint.replace(/^(?:https?|wss?):\\/\\//, \"\").replace(/\\/+$/, \"\");\n return `${scheme}://${host}`;\n}\n\n/**\n * Create a `Transport` that connects to a relay endpoint and carries app traffic E2EE. `connect(url)`\n * dials `url` (the relay's own WebSocket address, NOT the daemon's — see {@link relayDialUrl}),\n * registers `sessionId` (or its derived default), and resolves only once the daemon's `e2ee_ready`\n * arrives — mirroring `createWebSocketTransport`'s contract of resolving once genuinely usable.\n */\nexport function createRelayTransport(opts: RelayTransportOptions): Transport {\n let ws: AnyWebSocket | null = null;\n let channel: EncryptedChannel | null = null;\n const sessionId = opts.sessionId ?? deriveRelaySessionId(opts.daemonPublicKey);\n\n const self: Transport = {\n onMessage: null,\n onClose: null,\n onError: null,\n get isOpen(): boolean {\n return ws !== null && ws.readyState === 1 && channel !== null && channel.ready;\n },\n connect(url: string): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const create = opts.factory ?? (() => new WebSocket(url) as unknown as AnyWebSocket);\n try {\n ws = opts.factory ? create(url) : (new WebSocket(url) as unknown as AnyWebSocket);\n } catch (err) {\n reject(err);\n return;\n }\n const socket = ws;\n\n const messageHandlers: Array<(data: string) => void> = [];\n const closeHandlers: Array<(reason?: string) => void> = [];\n\n socket.onopen = () => {\n socket.send(JSON.stringify({ type: \"relay_register\", sessionId }));\n channel = createClientChannel({\n transport: {\n send: (data) => socket.send(data),\n onMessage: (h) => messageHandlers.push(h),\n onClose: (h) => closeHandlers.push(h),\n close: () => socket.close(),\n },\n attachment: { sessionId },\n daemonPublicKey: opts.daemonPublicKey,\n events: {\n onReady: () => resolve(),\n onMessage: (plaintext) => self.onMessage?.(plaintext),\n onBinaryMessage: (bytes) =>\n self.onMessage?.(bytes.slice().buffer),\n onAuthError: (err) => self.onError?.(err),\n },\n });\n };\n socket.onerror = (ev) => {\n if (socket.readyState !== 1) reject(ev);\n self.onError?.(ev);\n };\n socket.onclose = (ev) => {\n for (const h of closeHandlers) h(reasonString(ev.reason));\n self.onClose?.(ev.code, reasonString(ev.reason));\n };\n socket.onmessage = (ev) => {\n const { data } = ev;\n if (typeof data === \"string\") {\n for (const h of messageHandlers) h(data);\n }\n // Binary frames never arrive as raw WS binary here — the relay channel's `e2ee_bin`\n // wire frame is itself a JSON text frame (base64-wrapped ciphertext), so all decrypted\n // binary payloads surface via `onBinaryMessage` above, not this raw-binary branch.\n };\n });\n },\n sendText(data: string): void {\n if (!channel || !channel.ready) {\n throw new Error(\"relay transport: cannot send before the E2EE handshake completes\");\n }\n channel.send(data);\n },\n sendBinary(data: Uint8Array): void {\n if (!channel || !channel.ready) {\n throw new Error(\"relay transport: cannot send before the E2EE handshake completes\");\n }\n channel.sendBinary(data);\n },\n close(code = 1000, reason = \"\"): void {\n channel?.close();\n channel = null;\n ws?.close(code, reason);\n ws = null;\n },\n };\n return self;\n}\n","/**\n * Client-side pairing helpers (architecture/relay-e2ee.md § Pairing). Parses the pairing URL\n * produced by `packages/cli/src/pairing.ts#buildPairingUrl` — the daemon's persistent Curve25519\n * public key rides in the URL **fragment** (`#offer=<base64>&host=<...>` or, for a relay-routed\n * daemon, `#offer=<base64>&relay=<endpoint>&relayTls=<0|1>`), which a browser/RN client never\n * sends to the web server hosting the pairing page (`app.pi-studio.sh`). Treat the decoded key\n * like a password (trust anchor) — see relay-e2ee.md § Pairing.\n */\nimport { decodeBase64 } from \"@av-pi-studio/relay\";\n\nexport interface PairingOffer {\n /** The daemon's persistent Curve25519 public key, base64-encoded exactly as carried in `offer=`. */\n publicKeyB64: string;\n /** The same key decoded to raw bytes, ready for `createClientChannel({ daemonPublicKey })`. */\n publicKey: Uint8Array;\n /** Optional direct host hint (`host=`), if the pairing link included one. Mutually exclusive with `relay`. */\n host?: string;\n /** Relay endpoint (`relay=`) + TLS flag (`relayTls=`), if the pairing link routes through a relay. */\n relay?: { endpoint: string; useTls: boolean };\n}\n\n/**\n * Parse a pairing URL's fragment for the `offer` (daemon public key) and either a direct `host`\n * hint or `relay`/`relayTls` relay-routing info. Accepts either a full URL\n * (`https://app.pi-studio.sh/#offer=...`) or a bare fragment (`#offer=...` / `offer=...`). Returns\n * `null` if no `offer` parameter is present — the caller must treat that as \"not a valid pairing\n * link\", never fall back to an unauthenticated connection.\n */\nexport function parsePairingUrl(input: string): PairingOffer | null {\n const hashIndex = input.indexOf(\"#\");\n const fragment = hashIndex === -1 ? input : input.slice(hashIndex + 1);\n const params = new URLSearchParams(fragment);\n const publicKeyB64 = params.get(\"offer\");\n if (!publicKeyB64) return null;\n\n let publicKey: Uint8Array;\n try {\n publicKey = decodeBase64(publicKeyB64);\n } catch {\n return null;\n }\n\n const relayEndpoint = params.get(\"relay\");\n if (relayEndpoint) {\n const relay = { endpoint: relayEndpoint, useTls: params.get(\"relayTls\") === \"1\" };\n return { publicKeyB64, publicKey, relay };\n }\n\n const host = params.get(\"host\") ?? undefined;\n return { publicKeyB64, publicKey, ...(host ? { host } : {}) };\n}\n","import {\n decodeTerminalFrame,\n tryDecodeFileTransferFrame,\n FileTransferOpcode,\n type ClientType,\n type ServerInfoPayload,\n type SessionMessage,\n type TerminalFrame,\n type FileTransferFrame,\n} from \"@av-pi-studio/protocol\";\n\nimport type { Transport } from \"./transport.js\";\nimport { createWebSocketTransport } from \"./transport.js\";\n\n/**\n * Low-level `DaemonClient` WebSocket driver: connect, hello handshake, framing, RPC correlation,\n * ping/pong liveness, and a pluggable transport abstraction.\n *\n * See architecture/client-app-runtime.md § Connection and architecture/websocket-protocol.md\n * § Connection & handshake, § Top-level envelopes.\n *\n * Out of scope here: the `Pi-StudioClient` facade (task-002), relay transport (sprint-013), and the\n * terminal-stream router (task-003) — though this driver exposes the hooks the router rides on.\n */\n\n/** Connection lifecycle states. */\nexport type ConnectionState = \"idle\" | \"connecting\" | \"open\" | \"closing\" | \"closed\";\n\n/** The default wire protocol version this client speaks. */\nexport const PROTOCOL_VERSION = 1;\n\n/** Portable random id — `crypto.randomUUID` where available (browser secure context, RN Hermes\n * 0.74+, Node 16+), else a timestamp+random fallback for insecure/older contexts. */\nexport function randomId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n return `id-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport interface DaemonClientOptions {\n url: string;\n clientId: string;\n clientType: ClientType;\n protocolVersion?: number;\n appVersion?: string;\n capabilities?: Record<string, boolean>;\n /** Inject a transport (tests/relay). Defaults to a direct WebSocket transport. */\n transport?: Transport;\n /** Default RPC timeout (ms). RPC timeouts are operation errors, NOT socket deaths. */\n rpcTimeoutMs?: number;\n /** Optional clock for deterministic tests. */\n now?: () => number;\n}\n\n/** A pending RPC awaiting its correlated response. */\ninterface PendingRpc {\n resolve: (payload: unknown) => void;\n reject: (error: Error) => void;\n timer: ReturnType<typeof setTimeout> | null;\n}\n\n/** A failed RPC surfaced from a correlated `rpc_error`. */\nexport class RpcError extends Error {\n constructor(\n message: string,\n readonly requestId: string,\n readonly code?: string,\n ) {\n super(message);\n this.name = \"RpcError\";\n }\n}\n\n/** A timed-out RPC. Distinguished from a dead socket per the protocol contract. */\nexport class RpcTimeoutError extends Error {\n constructor(readonly requestId: string) {\n super(`RPC ${requestId} timed out`);\n this.name = \"RpcTimeoutError\";\n }\n}\n\nexport type SessionMessageHandler = (message: SessionMessage) => void;\nexport type TerminalFrameHandler = (frame: TerminalFrame) => void;\nexport type FileTransferFrameHandler = (frame: FileTransferFrame) => void;\nexport type StateChangeHandler = (state: ConnectionState) => void;\n\nexport class DaemonClient {\n private readonly transport: Transport;\n private readonly rpcTimeoutMs: number;\n private readonly now: () => number;\n\n private _state: ConnectionState = \"idle\";\n private _serverId: string | null = null;\n private _features: Record<string, unknown> = {};\n private _serverCapabilities: Record<string, unknown> = {};\n\n private readonly pending = new Map<string, PendingRpc>();\n private readonly sessionHandlers = new Set<SessionMessageHandler>();\n private readonly terminalHandlers = new Set<TerminalFrameHandler>();\n private readonly fileTransferHandlers = new Set<FileTransferFrameHandler>();\n private readonly stateHandlers = new Set<StateChangeHandler>();\n\n private helloResolve: ((info: ServerInfoPayload) => void) | null = null;\n private helloReject: ((error: Error) => void) | null = null;\n\n constructor(private readonly options: DaemonClientOptions) {\n this.transport = options.transport ?? createWebSocketTransport();\n this.rpcTimeoutMs = options.rpcTimeoutMs ?? 30_000;\n this.now = options.now ?? (() => Date.now());\n\n this.transport.onMessage = (data) => this.handleIncoming(data);\n this.transport.onClose = (code, reason) => this.handleClose(code, reason);\n this.transport.onError = () => {\n /* surfaced via close / rpc errors; do not treat as fatal on its own */\n };\n }\n\n // ─── Public accessors ─────────────────────────────────────────────────────\n\n get state(): ConnectionState {\n return this._state;\n }\n get serverId(): string | null {\n return this._serverId;\n }\n get features(): Record<string, unknown> {\n return this._features;\n }\n get serverCapabilities(): Record<string, unknown> {\n return this._serverCapabilities;\n }\n /** True iff the daemon advertised the given `server_info.features.*` flag truthy. */\n hasFeature(flag: string): boolean {\n return Boolean(this._features[flag]);\n }\n\n // ─── Lifecycle ────────────────────────────────────────────────────────────\n\n /**\n * Open the socket, send `hello`, and await `status`/`server_info`. Resolves with the\n * recorded server info (serverId + features).\n */\n async connect(): Promise<ServerInfoPayload> {\n this.setState(\"connecting\");\n await this.transport.connect(this.options.url);\n\n const handshake = new Promise<ServerInfoPayload>((resolve, reject) => {\n this.helloResolve = resolve;\n this.helloReject = reject;\n });\n\n this.transport.sendText(\n JSON.stringify({\n type: \"hello\",\n clientId: this.options.clientId,\n clientType: this.options.clientType,\n protocolVersion: this.options.protocolVersion ?? PROTOCOL_VERSION,\n appVersion: this.options.appVersion,\n capabilities: this.options.capabilities,\n }),\n );\n\n const info = await handshake;\n this._serverId = info.serverId;\n this._features = info.features;\n this._serverCapabilities = info.capabilities;\n this.setState(\"open\");\n return info;\n }\n\n /** Close the socket cleanly. */\n close(code = 1000, reason = \"\"): void {\n this.setState(\"closing\");\n this.transport.close(code, reason);\n }\n\n // ─── RPC / send ───────────────────────────────────────────────────────────\n\n /**\n * Send a correlated session RPC. Resolves with the response `payload` when a session message\n * carrying the same `requestId` arrives, rejects with `RpcError` on a correlated `rpc_error`,\n * and rejects with `RpcTimeoutError` (operation error only, socket stays alive) on timeout.\n */\n request<T = unknown>(\n type: string,\n params: Record<string, unknown> = {},\n timeoutMs?: number,\n ): Promise<T> {\n const requestId = (params.requestId as string | undefined) ?? randomId();\n return new Promise<T>((resolve, reject) => {\n const timeout = timeoutMs ?? this.rpcTimeoutMs;\n const timer =\n timeout > 0\n ? setTimeout(() => {\n this.pending.delete(requestId);\n reject(new RpcTimeoutError(requestId));\n }, timeout)\n : null;\n this.pending.set(requestId, {\n resolve: (payload) => resolve(payload as T),\n reject,\n timer,\n });\n this.sendSession({ type, ...params, requestId } as unknown as SessionMessage);\n });\n }\n\n /** Send a fire-and-forget session message (wrapped in a `session` envelope). */\n sendSession(message: SessionMessage): void {\n this.transport.sendText(JSON.stringify({ type: \"session\", message }));\n }\n\n /** Send a raw binary frame (terminal / file-transfer). */\n sendBinary(data: Uint8Array): void {\n this.transport.sendBinary(data);\n }\n\n /**\n * Send a JSON `ping` and await the correlated `pong`. Liveness rides on the data path because\n * browser/RN WebSocket APIs do not expose RFC6455 ping.\n */\n ping(timeoutMs = 10_000): Promise<void> {\n const requestId = randomId();\n return new Promise<void>((resolve, reject) => {\n const timer =\n timeoutMs > 0\n ? setTimeout(() => {\n this.pending.delete(requestId);\n reject(new RpcTimeoutError(requestId));\n }, timeoutMs)\n : null;\n this.pending.set(requestId, {\n resolve: () => resolve(),\n reject,\n timer,\n });\n this.transport.sendText(\n JSON.stringify({ type: \"ping\", requestId, clientSentAt: this.now() }),\n );\n });\n }\n\n // ─── Subscriptions ──────────────────────────────────────────────────────────\n\n /** Subscribe to all inbound session messages (e.g. `agent_stream`, `agent_update`). */\n onSessionMessage(handler: SessionMessageHandler): () => void {\n this.sessionHandlers.add(handler);\n return () => this.sessionHandlers.delete(handler);\n }\n\n /** Subscribe to decoded inbound terminal binary frames. */\n onTerminalFrame(handler: TerminalFrameHandler): () => void {\n this.terminalHandlers.add(handler);\n return () => this.terminalHandlers.delete(handler);\n }\n\n /** Subscribe to decoded inbound file-transfer binary frames (download/upload chunks). */\n onFileTransferFrame(handler: FileTransferFrameHandler): () => void {\n this.fileTransferHandlers.add(handler);\n return () => this.fileTransferHandlers.delete(handler);\n }\n\n /** Subscribe to connection-state transitions. */\n onStateChange(handler: StateChangeHandler): () => void {\n this.stateHandlers.add(handler);\n return () => this.stateHandlers.delete(handler);\n }\n\n // ─── Internals ──────────────────────────────────────────────────────────────\n\n private setState(next: ConnectionState): void {\n if (this._state === next) return;\n this._state = next;\n for (const handler of this.stateHandlers) handler(next);\n }\n\n private handleClose(_code: number, reason: string): void {\n // Reject the in-flight handshake if the socket closed before server_info.\n if (this.helloReject) {\n this.helloReject(new Error(`socket closed during handshake: ${reason}`));\n this.helloResolve = null;\n this.helloReject = null;\n }\n // Reject all pending RPCs — the socket is gone (distinct from an RPC timeout).\n for (const [, pending] of this.pending) {\n if (pending.timer) clearTimeout(pending.timer);\n pending.reject(new Error(`socket closed: ${reason}`));\n }\n this.pending.clear();\n this.setState(\"closed\");\n }\n\n private handleIncoming(data: string | ArrayBuffer | Blob): void {\n if (typeof data === \"string\") {\n this.handleTextFrame(data);\n return;\n }\n if (data instanceof ArrayBuffer) {\n this.handleBinaryFrame(new Uint8Array(data));\n return;\n }\n // Blob (browser) — read asynchronously.\n if (typeof (data as Blob).arrayBuffer === \"function\") {\n void (data as Blob).arrayBuffer().then((buf) => this.handleBinaryFrame(new Uint8Array(buf)));\n }\n }\n\n /**\n * Route an inbound binary frame by its opcode byte: file-transfer opcodes (`0x10`–`0x13`) go to\n * `fileTransferHandlers`, everything else decodes as a terminal frame. Two independent binary\n * protocols share one opcode byte's numeric range without colliding — see\n * `@av-pi-studio/protocol`'s `TerminalOpcode` (`0x01`–`0x05`) vs `FileTransferOpcode`\n * (`0x10`–`0x13`).\n */\n private handleBinaryFrame(bytes: Uint8Array): void {\n const opcodeByte = bytes[0];\n const isFileTransfer =\n opcodeByte !== undefined &&\n Object.values(FileTransferOpcode).includes(opcodeByte as never);\n if (isFileTransfer) {\n const frame = tryDecodeFileTransferFrame(bytes);\n if (!frame) return; // malformed frame — drop rather than throw\n for (const handler of this.fileTransferHandlers) handler(frame);\n return;\n }\n const frame = decodeTerminalFrame(bytes);\n for (const handler of this.terminalHandlers) handler(frame);\n }\n\n private handleTextFrame(raw: string): void {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return; // ignore malformed frames\n }\n const envelope = parsed as Record<string, unknown>;\n switch (envelope.type) {\n case \"status\":\n this.handleStatus(envelope);\n return;\n case \"pong\":\n this.resolvePending(envelope.requestId as string, undefined);\n return;\n case \"ping\":\n // Some daemons send keepalive pings; reply with a pong.\n this.transport.sendText(\n JSON.stringify({\n type: \"pong\",\n requestId: envelope.requestId,\n serverReceivedAt: this.now(),\n serverSentAt: this.now(),\n }),\n );\n return;\n case \"session\":\n this.handleSession(envelope.message as SessionMessage);\n return;\n default:\n return; // unknown top-level envelope — ignore\n }\n }\n\n private handleStatus(envelope: Record<string, unknown>): void {\n const payload = envelope.payload as ServerInfoPayload | undefined;\n if (payload?.status === \"server_info\" && this.helloResolve) {\n this.helloResolve(payload);\n this.helloResolve = null;\n this.helloReject = null;\n }\n }\n\n private handleSession(message: SessionMessage | undefined): void {\n if (!message || typeof message !== \"object\") return;\n const msg = message as unknown as Record<string, unknown>;\n\n // Correlated rpc_error → reject the matching pending RPC.\n if (msg.type === \"rpc_error\" && typeof msg.requestId === \"string\") {\n const pending = this.pending.get(msg.requestId);\n if (pending) {\n if (pending.timer) clearTimeout(pending.timer);\n this.pending.delete(msg.requestId);\n pending.reject(\n new RpcError(\n (msg.message as string) ?? (msg.error as string) ?? \"RPC failed\",\n msg.requestId,\n msg.code as string | undefined,\n ),\n );\n }\n return;\n }\n\n // Correlated response → resolve the matching pending RPC (and still fan out).\n if (typeof msg.requestId === \"string\" && this.pending.has(msg.requestId)) {\n const payload = \"payload\" in msg ? msg.payload : msg;\n this.resolvePending(msg.requestId, payload);\n }\n\n // Fan out to subscribers (streams, updates, uncorrelated broadcasts).\n for (const handler of this.sessionHandlers) handler(message);\n }\n\n private resolvePending(requestId: string, payload: unknown): void {\n const pending = this.pending.get(requestId);\n if (!pending) return;\n if (pending.timer) clearTimeout(pending.timer);\n this.pending.delete(requestId);\n pending.resolve(payload);\n }\n}\n","import type {\n AgentCloneResponse,\n AgentCompactResponse,\n AgentCycleModelResponse,\n AgentExportHtmlResponse,\n AgentForkMessagesResponse,\n AgentForkResponse,\n AgentLastAssistantTextResponse,\n AgentNewSessionResponse,\n AgentSessionStatsResponse,\n AgentSetModelResponse,\n AgentSetSessionNameResponse,\n AgentStreamEvent,\n AgentSwitchSessionResponse,\n CreateAgentRequest,\n FetchAgentTimelineResponse,\n SessionMessage,\n TimelineDirection,\n} from \"@av-pi-studio/protocol\";\nimport type { DaemonClient } from \"./daemon-client.js\";\n\n/**\n * High-level `PiStudioClient` SDK facade over the low-level `DaemonClient` driver.\n *\n * Exposes workspace/agent/provider handles plus update handler registration, mirroring the\n * scope's `Pi-StudioClient` surface (architecture/client-app-runtime.md § Layered client library /\n * Facade and features/agent-sessions.md § Public Contract). Method names follow the server RPC\n * type names (`create_agent_request`, `send_agent_prompt`, `interrupt_agent`, `update_agent`,\n * `resume_agent`, `import_agent_session`, `fetch_agent_timeline_request`, and the sprint-037\n * slash-command RPCs: `agent_session_stats_request`, `agent_compact_request`,\n * `agent_new_session_request`, `agent_switch_session_request`, `agent_fork_request`,\n * `agent_fork_messages_request`, `agent_clone_request`, `agent_set_session_name_request`,\n * `agent_export_html_request`, `agent_set_model_request`, `agent_cycle_model_request`,\n * `agent_last_assistant_text_request`).\n *\n * Out of scope: app runtime controller (sprint-012), terminal router (task-003).\n */\n\n// ─── Update handler types ─────────────────────────────────────────────────────\n\nexport type PiStudioAgentUpdateHandler = (update: AgentUpdateMessage) => void;\nexport type PiStudioWorkspaceUpdateHandler = (update: WorkspaceUpdateMessage) => void;\nexport type PiStudioAgentStreamHandler = (event: AgentStreamEvent) => void;\n\n/** Shape of an inbound `agent_update` session message (append-only; passthrough). */\nexport interface AgentUpdateMessage {\n type: \"agent_update\";\n agentId: string;\n status?: string;\n title?: string;\n labels?: Record<string, string>;\n [key: string]: unknown;\n}\n\n/** Shape of an inbound `workspace_update` session message. */\nexport interface WorkspaceUpdateMessage {\n type: \"workspace_update\";\n workspaceId: string;\n [key: string]: unknown;\n}\n\n// ─── Agent / timeline / workspace / provider handle interfaces ──────────────────\n\nexport interface PiStudioAgentTimelineHandle {\n /** Fetch a bounded page of authoritative timeline history. */\n fetch(opts?: {\n cursor?: string;\n direction?: TimelineDirection;\n limit?: number;\n }): Promise<FetchAgentTimelineResponse>;\n /** Subscribe to live `agent_stream` events for this agent. Returns an unsubscribe fn. */\n subscribe(handler: PiStudioAgentStreamHandler): () => void;\n}\n\nexport interface PiStudioAgentActions {\n readonly agentId: string;\n readonly timeline: PiStudioAgentTimelineHandle;\n /** Send a follow-up prompt to the running/idle agent. */\n send(prompt: string, opts?: { clientMessageId?: string; images?: unknown[] }): Promise<unknown>;\n /** Alias for `send` reflecting the scope's \"run a turn\" verb. */\n run(prompt: string, opts?: { clientMessageId?: string; images?: unknown[] }): Promise<unknown>;\n /** Interrupt the current turn. */\n interrupt(): Promise<unknown>;\n /** Steer the LIVE turn — inject a message delivered after the current assistant turn's tool\n * calls, before the next LLM call. Provider must support steering (Pi does). */\n steer(message: string, opts?: { clientMessageId?: string; images?: unknown[] }): Promise<unknown>;\n /** Queue a follow-up message delivered only after the agent fully stops. */\n followUp(\n message: string,\n opts?: { clientMessageId?: string; images?: unknown[] },\n ): Promise<unknown>;\n /** Update model/mode/thinking/features/title/labels without recreating the session. */\n update(patch: {\n modeId?: string;\n model?: string;\n thinkingOptionId?: string;\n featureValues?: Record<string, unknown>;\n title?: string;\n labels?: Record<string, string>;\n }): Promise<unknown>;\n /** Resume a closed session via its persistence handle. */\n resume(): Promise<unknown>;\n /** Archive (soft-delete) the agent — closes the runtime, keeps the record for resume. */\n archive(): Promise<unknown>;\n /** Permanently delete the agent's persisted record (hard delete — no trace, cannot resume). */\n delete(): Promise<unknown>;\n /** Subscribe to `agent_update` events scoped to this agent. */\n onUpdate(handler: PiStudioAgentUpdateHandler): () => void;\n\n // Slash-command operations (sprint-037): Pi built-ins with a real Pi RPC equivalent. Each\n // resolves to the RPC's `payload` (the daemon unwraps `{type, requestId, payload}` to just\n // `payload` for a correlated response — see DaemonClient.request's resolvePending contract).\n /** `/session` — read-only stats (tokens, cost, context-window usage). */\n sessionStats(): Promise<AgentSessionStatsResponse[\"payload\"]>;\n /** `/compact` — manually compact conversation context. */\n compact(customInstructions?: string): Promise<AgentCompactResponse[\"payload\"]>;\n /** `/new` — start a fresh session in place. */\n newSession(): Promise<AgentNewSessionResponse[\"payload\"]>;\n /** `/resume` — load a different session file in place. */\n switchSession(sessionPath: string): Promise<AgentSwitchSessionResponse[\"payload\"]>;\n /** `/fork` — create a new branch from a previous user message. */\n fork(entryId: string): Promise<AgentForkResponse[\"payload\"]>;\n /** Fork picker — user messages available to fork from. */\n forkMessages(): Promise<AgentForkMessagesResponse[\"payload\"]>;\n /** `/clone` — duplicate the active branch into a new session at the current position. */\n clone(): Promise<AgentCloneResponse[\"payload\"]>;\n /** `/name` — set the session display name. */\n setSessionName(name: string): Promise<AgentSetSessionNameResponse[\"payload\"]>;\n /** `/export` — export the session to an HTML file. */\n exportHtml(outputPath?: string): Promise<AgentExportHtmlResponse[\"payload\"]>;\n /** `/model` (set) — switch to a specific provider model. */\n setModel(provider: string, modelId: string): Promise<AgentSetModelResponse[\"payload\"]>;\n /** `/model` (cycle) — cycle to the next available model. */\n cycleModel(): Promise<AgentCycleModelResponse[\"payload\"]>;\n /** `/copy` — the text content of the last assistant message. */\n lastAssistantText(): Promise<AgentLastAssistantTextResponse[\"payload\"]>;\n}\n\nexport interface PiStudioWorkspaceActions {\n readonly workspaceId: string;\n /** Subscribe to `workspace_update` events scoped to this workspace. */\n onUpdate(handler: PiStudioWorkspaceUpdateHandler): () => void;\n}\n\nexport interface ProviderModel {\n id: string;\n label?: string;\n description?: string;\n /**\n * The model's own underlying LLM provider (e.g. `\"anthropic\"`) — REQUIRED by\n * `AgentHandle.setModel(provider, modelId)`'s `provider` argument. Distinct from the\n * `ListProviderModelsResponse.provider` field above, which is the pi-studio `AgentClient` id\n * (`\"pi\"`/`\"mock\"`) used only to pick which client answered this list.\n */\n provider?: string;\n}\n\nexport interface ListProviderModelsResponse {\n type: \"list_provider_models_response\";\n requestId: string;\n provider: string;\n models: ProviderModel[];\n}\n\nexport interface ResolveDefaultModelResponse {\n type: \"resolve_default_model_response\";\n requestId: string;\n provider: string;\n /** The model a brand-new session with no override would run on — settings' configured default,\n * else the provider's built-in default — or `undefined` if the provider can't resolve one\n * without spawning a session. Display-only: never itself sent back to the daemon as a pick. */\n model?: string;\n /** The resolved model's own underlying LLM provider (e.g. `\"anthropic\"`) — same distinction as\n * `ProviderModel.provider`, required to pin it via `config.modelProvider` if the draft\n * materializes without the user ever changing it. */\n modelProvider?: string;\n}\n\nexport interface PiStudioProviderActions {\n /** List available providers. */\n listProviders(): Promise<unknown>;\n /** List models for a provider. */\n listModels(provider: string): Promise<ListProviderModelsResponse>;\n /** List modes for a provider. */\n listModes(provider: string): Promise<unknown>;\n /** Resolve the model a brand-new session would run on with no override — backs the\n * web-client's \"preselect the default model on a new chat\" before anything is spawned. */\n resolveDefaultModel(provider: string, cwd?: string): Promise<ResolveDefaultModelResponse>;\n /** Trigger an explicit provider snapshot refresh (no hidden revalidation). */\n refreshSnapshot(): Promise<unknown>;\n}\n\n// ─── Facade implementation ──────────────────────────────────────────────────────\n\nexport class PiStudioClient {\n constructor(private readonly daemon: DaemonClient) {}\n\n get connection(): DaemonClient {\n return this.daemon;\n }\n\n /**\n * Create a new agent session. Returns the response payload (includes `agentId`). When\n * `initialPrompt` is set, the daemon runs the first turn and streams events to subscribers.\n */\n async createAgent(\n req: Omit<CreateAgentRequest, \"type\" | \"requestId\"> & { requestId?: string },\n ): Promise<{ agentId: string; [key: string]: unknown }> {\n const payload = await this.daemon.request<{ agentId: string }>(\"create_agent_request\", {\n ...req,\n });\n return payload as { agentId: string };\n }\n\n /** Get an agent handle for an existing agentId. */\n agent(agentId: string): PiStudioAgentActions {\n return new AgentHandle(this.daemon, agentId);\n }\n\n /** Get a workspace handle for an existing workspaceId. */\n workspace(workspaceId: string): PiStudioWorkspaceActions {\n return new WorkspaceHandle(this.daemon, workspaceId);\n }\n\n /** Provider actions (list providers/models/modes, refresh snapshot). */\n get providers(): PiStudioProviderActions {\n return new ProviderHandle(this.daemon);\n }\n\n /** Subscribe to ALL `agent_update` events (any agent). */\n onAgentUpdate(handler: PiStudioAgentUpdateHandler): () => void {\n return this.daemon.onSessionMessage((msg) => {\n if ((msg as { type?: string }).type === \"agent_update\") {\n handler(msg as unknown as AgentUpdateMessage);\n }\n });\n }\n\n /** Subscribe to ALL `workspace_update` events (any workspace). */\n onWorkspaceUpdate(handler: PiStudioWorkspaceUpdateHandler): () => void {\n return this.daemon.onSessionMessage((msg) => {\n if ((msg as { type?: string }).type === \"workspace_update\") {\n handler(msg as unknown as WorkspaceUpdateMessage);\n }\n });\n }\n}\n\n// ─── Handle classes ──────────────────────────────────────────────────────────────\n\nclass TimelineHandle implements PiStudioAgentTimelineHandle {\n constructor(\n private readonly daemon: DaemonClient,\n private readonly agentId: string,\n ) {}\n\n fetch(\n opts: { cursor?: string; direction?: TimelineDirection; limit?: number } = {},\n ): Promise<FetchAgentTimelineResponse> {\n return this.daemon.request<FetchAgentTimelineResponse>(\"fetch_agent_timeline_request\", {\n agentId: this.agentId,\n cursor: opts.cursor,\n direction: opts.direction,\n limit: opts.limit,\n });\n }\n\n subscribe(handler: PiStudioAgentStreamHandler): () => void {\n return this.daemon.onSessionMessage((msg) => {\n const m = msg as unknown as { type?: string; agentId?: string; event?: AgentStreamEvent };\n if (m.type === \"agent_stream\" && m.agentId === this.agentId && m.event) {\n handler(m.event);\n }\n });\n }\n}\n\nclass AgentHandle implements PiStudioAgentActions {\n readonly timeline: PiStudioAgentTimelineHandle;\n\n constructor(\n private readonly daemon: DaemonClient,\n readonly agentId: string,\n ) {\n this.timeline = new TimelineHandle(daemon, agentId);\n }\n\n send(\n prompt: string,\n opts: { clientMessageId?: string; images?: unknown[] } = {},\n ): Promise<unknown> {\n return this.daemon.request(\"send_agent_prompt\", {\n agentId: this.agentId,\n prompt,\n clientMessageId: opts.clientMessageId,\n images: opts.images,\n });\n }\n\n run(prompt: string, opts?: { clientMessageId?: string; images?: unknown[] }): Promise<unknown> {\n return this.send(prompt, opts);\n }\n\n interrupt(): Promise<unknown> {\n return this.daemon.request(\"interrupt_agent\", { agentId: this.agentId });\n }\n\n steer(\n message: string,\n opts: { clientMessageId?: string; images?: unknown[] } = {},\n ): Promise<unknown> {\n return this.daemon.request(\"steer_agent_request\", {\n agentId: this.agentId,\n message,\n clientMessageId: opts.clientMessageId,\n images: opts.images,\n });\n }\n\n followUp(\n message: string,\n opts: { clientMessageId?: string; images?: unknown[] } = {},\n ): Promise<unknown> {\n return this.daemon.request(\"follow_up_agent_request\", {\n agentId: this.agentId,\n message,\n clientMessageId: opts.clientMessageId,\n images: opts.images,\n });\n }\n\n update(patch: {\n modeId?: string;\n model?: string;\n thinkingOptionId?: string;\n featureValues?: Record<string, unknown>;\n title?: string;\n labels?: Record<string, string>;\n }): Promise<unknown> {\n return this.daemon.request(\"update_agent\", { agentId: this.agentId, ...patch });\n }\n\n resume(): Promise<unknown> {\n return this.daemon.request(\"resume_agent\", { agentId: this.agentId });\n }\n\n archive(): Promise<unknown> {\n return this.daemon.request(\"archive_agent\", { agentId: this.agentId });\n }\n\n delete(): Promise<unknown> {\n return this.daemon.request(\"delete_agent\", { agentId: this.agentId });\n }\n\n onUpdate(handler: PiStudioAgentUpdateHandler): () => void {\n return this.daemon.onSessionMessage((msg) => {\n const m = msg as unknown as AgentUpdateMessage;\n if (m.type === \"agent_update\" && m.agentId === this.agentId) handler(m);\n });\n }\n\n // Slash-command operations (sprint-037).\n\n sessionStats(): Promise<AgentSessionStatsResponse[\"payload\"]> {\n return this.daemon.request(\"agent_session_stats_request\", { agentId: this.agentId });\n }\n\n compact(customInstructions?: string): Promise<AgentCompactResponse[\"payload\"]> {\n return this.daemon.request(\"agent_compact_request\", {\n agentId: this.agentId,\n customInstructions,\n });\n }\n\n newSession(): Promise<AgentNewSessionResponse[\"payload\"]> {\n return this.daemon.request(\"agent_new_session_request\", { agentId: this.agentId });\n }\n\n switchSession(sessionPath: string): Promise<AgentSwitchSessionResponse[\"payload\"]> {\n return this.daemon.request(\"agent_switch_session_request\", {\n agentId: this.agentId,\n sessionPath,\n });\n }\n\n fork(entryId: string): Promise<AgentForkResponse[\"payload\"]> {\n return this.daemon.request(\"agent_fork_request\", { agentId: this.agentId, entryId });\n }\n\n forkMessages(): Promise<AgentForkMessagesResponse[\"payload\"]> {\n return this.daemon.request(\"agent_fork_messages_request\", { agentId: this.agentId });\n }\n\n clone(): Promise<AgentCloneResponse[\"payload\"]> {\n return this.daemon.request(\"agent_clone_request\", { agentId: this.agentId });\n }\n\n setSessionName(name: string): Promise<AgentSetSessionNameResponse[\"payload\"]> {\n return this.daemon.request(\"agent_set_session_name_request\", {\n agentId: this.agentId,\n name,\n });\n }\n\n exportHtml(outputPath?: string): Promise<AgentExportHtmlResponse[\"payload\"]> {\n return this.daemon.request(\"agent_export_html_request\", {\n agentId: this.agentId,\n outputPath,\n });\n }\n\n setModel(provider: string, modelId: string): Promise<AgentSetModelResponse[\"payload\"]> {\n return this.daemon.request(\"agent_set_model_request\", {\n agentId: this.agentId,\n provider,\n modelId,\n });\n }\n\n cycleModel(): Promise<AgentCycleModelResponse[\"payload\"]> {\n return this.daemon.request(\"agent_cycle_model_request\", { agentId: this.agentId });\n }\n\n lastAssistantText(): Promise<AgentLastAssistantTextResponse[\"payload\"]> {\n return this.daemon.request(\"agent_last_assistant_text_request\", { agentId: this.agentId });\n }\n}\n\nclass WorkspaceHandle implements PiStudioWorkspaceActions {\n constructor(\n private readonly daemon: DaemonClient,\n readonly workspaceId: string,\n ) {}\n\n onUpdate(handler: PiStudioWorkspaceUpdateHandler): () => void {\n return this.daemon.onSessionMessage((msg) => {\n const m = msg as unknown as WorkspaceUpdateMessage;\n if (m.type === \"workspace_update\" && m.workspaceId === this.workspaceId) handler(m);\n });\n }\n}\n\nclass ProviderHandle implements PiStudioProviderActions {\n constructor(private readonly daemon: DaemonClient) {}\n\n listProviders(): Promise<unknown> {\n return this.daemon.request(\"list_providers\", {});\n }\n listModels(provider: string): Promise<ListProviderModelsResponse> {\n return this.daemon.request<ListProviderModelsResponse>(\"list_provider_models\", { provider });\n }\n listModes(provider: string): Promise<unknown> {\n return this.daemon.request(\"list_provider_modes\", { provider });\n }\n resolveDefaultModel(provider: string, cwd?: string): Promise<ResolveDefaultModelResponse> {\n return this.daemon.request<ResolveDefaultModelResponse>(\"resolve_default_model\", {\n provider,\n ...(cwd ? { cwd } : {}),\n });\n }\n refreshSnapshot(): Promise<unknown> {\n // Dotted-namespace RPC per the protocol convention.\n return this.daemon.request(\"providers.snapshot.refresh.request\", {});\n }\n}\n\n/** Convenience re-export of the import operation (not agent-scoped). */\nexport function importAgentSession(\n daemon: DaemonClient,\n args: { provider: string; cwd: string; providerHandleId: string },\n): Promise<unknown> {\n return daemon.request(\"import_agent_session\", args);\n}\n\nexport type { SessionMessage };\n","import { encodeFileTransferFrame, type FileTransferFrame } from \"@av-pi-studio/protocol\";\n\nimport { randomId, type DaemonClient } from \"./daemon-client.js\";\n\n/**\n * Client-side demux + assembly for binary file downloads plus chunked uploads\n * (features/file-explorer-transfer.md § Binary transfer frames). Mirrors `TerminalStreamRouter`'s\n * inbound-frame-routing shape, but a download is a one-shot request/response rather than a\n * persistent per-slot subscription: request a token, request the download, await the assembled\n * bytes. An upload is the inverse: request an upload stream, then push `Begin → Chunk* → End`\n * binary frames the server writes to the target path.\n */\n\n/** Matches the server's default chunk size (`FileTransferService` DEFAULT_CHUNK_BYTES). */\nconst UPLOAD_CHUNK_BYTES = 32 * 1024;\n\nexport interface DownloadedFile {\n bytes: Uint8Array;\n fileName?: string;\n mimeType?: string;\n}\n\ninterface PendingDownload {\n chunks: Uint8Array[];\n meta: { fileName?: string; mimeType?: string };\n resolve: (file: DownloadedFile) => void;\n reject: (error: Error) => void;\n}\n\ninterface TokenResponse {\n ok: boolean;\n token?: string;\n error?: string;\n}\n\ninterface DownloadRequestResponse {\n ok: boolean;\n error?: string;\n}\n\ninterface UploadRequestResponse {\n ok: boolean;\n stream?: number;\n error?: string;\n}\n\nfunction concat(chunks: Uint8Array[]): Uint8Array {\n const total = chunks.reduce((sum, c) => sum + c.length, 0);\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.length;\n }\n return out;\n}\n\nexport class FileTransferClient {\n private readonly pending = new Map<number, PendingDownload>();\n private nextStream = 1;\n private detach: (() => void) | null = null;\n\n constructor(private readonly daemon: DaemonClient) {}\n\n /** Begin routing inbound file-transfer frames. Idempotent. */\n start(): void {\n if (this.detach) return;\n this.detach = this.daemon.onFileTransferFrame((frame) => this.dispatch(frame));\n }\n\n /** Stop routing inbound frames. Any downloads still pending are rejected. */\n stop(): void {\n this.detach?.();\n this.detach = null;\n for (const [stream, pending] of this.pending) {\n pending.reject(new Error(\"file transfer router stopped\"));\n this.pending.delete(stream);\n }\n }\n\n /**\n * Download the file at `path`: requests a single-use token, then requests the chunked\n * transfer, assembling `Begin → Chunk* → End` frames into one buffer.\n */\n async download(path: string): Promise<DownloadedFile> {\n const tokenResponse = await this.daemon.request<TokenResponse>(\n \"file_download_token_request\",\n { path },\n );\n if (!tokenResponse.ok || !tokenResponse.token) {\n throw new Error(tokenResponse.error ?? \"failed to issue download token\");\n }\n\n const stream = this.nextStream++;\n const result = new Promise<DownloadedFile>((resolve, reject) => {\n this.pending.set(stream, { chunks: [], meta: {}, resolve, reject });\n });\n\n const requestResponse = await this.daemon.request<DownloadRequestResponse>(\n \"file_download_request\",\n { token: tokenResponse.token, stream },\n );\n if (!requestResponse.ok) {\n this.pending.delete(stream);\n throw new Error(requestResponse.error ?? \"download request failed\");\n }\n\n return result;\n }\n\n /**\n * Upload `bytes` to `path` on the daemon: requests an upload stream, then pushes\n * `Begin → Chunk* → End` binary frames the server writes to disk (creating parent dirs,\n * overwriting any existing file). The server keys the transfer by `transferId`, which the\n * `Begin` frame must echo. Resolves once every frame is flushed to the socket.\n */\n async upload(path: string, bytes: Uint8Array): Promise<void> {\n const transferId = randomId();\n const response = await this.daemon.request<UploadRequestResponse>(\"file_upload_request\", {\n path,\n transferId,\n });\n if (!response.ok || response.stream === undefined) {\n throw new Error(response.error ?? \"upload request failed\");\n }\n const stream = response.stream;\n\n this.daemon.sendBinary(encodeFileTransferFrame({ opcode: \"Begin\", stream, meta: { transferId } }));\n for (let offset = 0; offset < bytes.length; offset += UPLOAD_CHUNK_BYTES) {\n const data = bytes.subarray(offset, offset + UPLOAD_CHUNK_BYTES);\n this.daemon.sendBinary(encodeFileTransferFrame({ opcode: \"Chunk\", stream, data }));\n }\n this.daemon.sendBinary(encodeFileTransferFrame({ opcode: \"End\", stream, ok: true }));\n }\n\n private dispatch(frame: FileTransferFrame): void {\n const pending = this.pending.get(frame.stream);\n if (!pending) return; // not a download stream we're tracking (e.g. an upload) — ignore\n switch (frame.opcode) {\n case \"Begin\":\n pending.meta.fileName = frame.meta.fileName;\n pending.meta.mimeType = frame.meta.mimeType;\n return;\n case \"Chunk\":\n pending.chunks.push(frame.data);\n return;\n case \"End\":\n this.pending.delete(frame.stream);\n if (frame.ok) {\n pending.resolve({ bytes: concat(pending.chunks), ...pending.meta });\n } else {\n pending.reject(new Error(\"download failed\"));\n }\n return;\n case \"Error\":\n this.pending.delete(frame.stream);\n pending.reject(new Error(frame.message));\n return;\n }\n }\n}\n","import type { DaemonClient } from \"./daemon-client.js\";\n\n/**\n * Reconnection + capability-rehydrate driver (architecture/client-app-runtime.md § Connection,\n * § Error Handling; architecture/websocket-protocol.md § Behavior / on reconnect).\n *\n * On socket drop, backoff-reconnect, re-`hello` (the `DaemonClient.connect()` resends the same\n * capabilities), and rehydrate the recorded `serverId`/`features`. Timeline resume planning lives\n * in sprint-012; this driver exposes the hooks (`onReconnected`) the planner rides on.\n *\n * Reconnection backoff parameters are TODO(verify) — defaults below are conservative.\n */\n\nexport interface ReconnectionOptions {\n /** Initial backoff delay (ms). Default 500. */\n initialDelayMs?: number;\n /** Maximum backoff delay (ms). Default 30_000. */\n maxDelayMs?: number;\n /** Exponential growth factor. Default 2. */\n factor?: number;\n /** Random jitter ratio [0..1] applied to each delay. Default 0.2. */\n jitter?: number;\n /** Max reconnect attempts before giving up (Infinity = never). Default Infinity. */\n maxAttempts?: number;\n /** Injected timer scheduler (tests). Defaults to setTimeout. */\n setTimer?: (cb: () => void, ms: number) => unknown;\n clearTimer?: (handle: unknown) => void;\n /** Injected RNG in [0,1) (tests). Defaults to Math.random. */\n random?: () => number;\n}\n\nexport type ReconnectedHandler = (info: { attempt: number; serverId: string | null }) => void;\nexport type ReconnectFailedHandler = (error: unknown, attempt: number) => void;\n\nexport class ReconnectionManager {\n private readonly initialDelayMs: number;\n private readonly maxDelayMs: number;\n private readonly factor: number;\n private readonly jitter: number;\n private readonly maxAttempts: number;\n private readonly setTimer: (cb: () => void, ms: number) => unknown;\n private readonly clearTimer: (handle: unknown) => void;\n private readonly random: () => number;\n\n private attempt = 0;\n private timer: unknown = null;\n private active = false;\n private detachState: (() => void) | null = null;\n\n private readonly reconnectedHandlers = new Set<ReconnectedHandler>();\n private readonly failedHandlers = new Set<ReconnectFailedHandler>();\n\n constructor(\n private readonly daemon: DaemonClient,\n options: ReconnectionOptions = {},\n ) {\n this.initialDelayMs = options.initialDelayMs ?? 500;\n this.maxDelayMs = options.maxDelayMs ?? 30_000;\n this.factor = options.factor ?? 2;\n this.jitter = options.jitter ?? 0.2;\n this.maxAttempts = options.maxAttempts ?? Number.POSITIVE_INFINITY;\n this.setTimer = options.setTimer ?? ((cb, ms) => setTimeout(cb, ms));\n this.clearTimer =\n options.clearTimer ?? ((h) => clearTimeout(h as ReturnType<typeof setTimeout>));\n this.random = options.random ?? Math.random;\n }\n\n /** Compute the backoff delay for a given attempt number (1-based), with jitter. */\n delayForAttempt(attempt: number): number {\n const base = Math.min(this.maxDelayMs, this.initialDelayMs * this.factor ** (attempt - 1));\n const jitterAmount = base * this.jitter * (this.random() * 2 - 1);\n return Math.max(0, Math.round(base + jitterAmount));\n }\n\n /** Subscribe to successful reconnections (after re-handshake + rehydrate). */\n onReconnected(handler: ReconnectedHandler): () => void {\n this.reconnectedHandlers.add(handler);\n return () => this.reconnectedHandlers.delete(handler);\n }\n\n /** Subscribe to reconnect attempt failures. */\n onReconnectFailed(handler: ReconnectFailedHandler): () => void {\n this.failedHandlers.add(handler);\n return () => this.failedHandlers.delete(handler);\n }\n\n /**\n * Start watching the daemon connection. On a `closed` transition, schedule a backoff reconnect.\n */\n start(): void {\n if (this.active) return;\n this.active = true;\n this.detachState = this.daemon.onStateChange((state) => {\n if (state === \"closed\" && this.active) this.scheduleReconnect();\n if (state === \"open\") this.attempt = 0; // reset backoff on a healthy connection\n });\n }\n\n /** Stop watching and cancel any pending reconnect. */\n stop(): void {\n this.active = false;\n if (this.timer) this.clearTimer(this.timer);\n this.timer = null;\n this.detachState?.();\n this.detachState = null;\n }\n\n /** Number of reconnect attempts made since the last healthy open. */\n get attemptCount(): number {\n return this.attempt;\n }\n\n private scheduleReconnect(): void {\n if (!this.active) return;\n if (this.attempt >= this.maxAttempts) return;\n this.attempt += 1;\n const delay = this.delayForAttempt(this.attempt);\n this.timer = this.setTimer(() => {\n this.timer = null;\n void this.tryReconnect();\n }, delay);\n }\n\n private async tryReconnect(): Promise<void> {\n if (!this.active) return;\n const attempt = this.attempt;\n try {\n // connect() re-sends hello with the same capabilities → daemon rehydrates them, and the\n // client re-records serverId + features from the fresh server_info.\n await this.daemon.connect();\n for (const handler of this.reconnectedHandlers) {\n handler({ attempt, serverId: this.daemon.serverId });\n }\n } catch (error) {\n for (const handler of this.failedHandlers) handler(error, attempt);\n this.scheduleReconnect(); // back off and retry\n }\n }\n}\n","/**\n * Normalize a user-typed daemon address into the `ws://` / `wss://` URL that\n * `DaemonClient` (via the browser `WebSocket`) actually opens.\n *\n * The daemon is a single HTTP server that upgrades to WebSocket on the same port, so users may\n * type whichever scheme they're familiar with:\n * - `http://host[:port]` → `ws://host[:port]`\n * - `https://host[:port]` → `wss://host[:port]`\n * - `ws://` / `wss://` → used as-is\n * - bare `host[:port]` → `ws://host[:port]`\n *\n * `new WebSocket(\"http://…\")` throws in browsers, so this MUST run before the URL reaches the\n * transport. Trailing slashes are trimmed; the input is otherwise left intact (path, query).\n */\nexport function normalizeDaemonUrl(input: string): string {\n const raw = input.trim();\n if (raw === \"\") return raw;\n\n const schemeMatch = raw.match(/^([a-z][a-z0-9+.-]*):\\/\\//i);\n if (!schemeMatch) {\n // Bare host or host:port.\n return `ws://${raw.replace(/\\/+$/, \"\")}`;\n }\n\n const scheme = (schemeMatch[1] as string).toLowerCase();\n const rest = raw.slice(schemeMatch[0].length).replace(/\\/+$/, \"\");\n\n switch (scheme) {\n case \"http\":\n return `ws://${rest}`;\n case \"https\":\n return `wss://${rest}`;\n case \"ws\":\n case \"wss\":\n return `${scheme}://${rest}`;\n default:\n // Unknown scheme — leave it to the transport to reject with a clear error.\n return raw;\n }\n}\n","/**\n * Pure decision logic for `connect()`: given whatever the user typed or pasted into the host\n * field, decide whether this is a direct daemon address or a pairing link (optionally\n * relay-routed) — see `pi-studio daemon pair` / architecture/relay-e2ee.md § Pairing.\n *\n * Kept separate from `connection-store.ts` (which has the side-effecting `DaemonClient`/transport\n * construction) so the routing decision itself is unit-testable without a live socket.\n */\nimport { parsePairingUrl, relayDialUrl } from \"@av-pi-studio/client\";\nimport { normalizeDaemonUrl } from \"./normalize-url.js\";\n\nexport type ConnectTarget =\n | { mode: \"direct\"; url: string }\n | { mode: \"relay\"; url: string; daemonPublicKey: Uint8Array };\n\n/**\n * Resolve what to connect to and how. `input` may be a plain daemon address (`ws://`, `http://`,\n * bare `host:port`) or a full pairing link. A pairing link with a `relay` offer routes to\n * `{ mode: \"relay\", ... }` (the relay's own dial URL, not the daemon's); a pairing link with a\n * `host` hint routes to `{ mode: \"direct\", url: <that host> }`; anything else is treated as a\n * plain daemon address, normalized via {@link normalizeDaemonUrl}.\n */\nexport function resolveConnectTarget(input: string): ConnectTarget {\n const offer = parsePairingUrl(input);\n if (offer?.relay) {\n return { mode: \"relay\", url: relayDialUrl(offer.relay), daemonPublicKey: offer.publicKey };\n }\n if (offer?.host) {\n return { mode: \"direct\", url: normalizeDaemonUrl(offer.host) };\n }\n return { mode: \"direct\", url: normalizeDaemonUrl(input) };\n}\n","/**\n * Connection store — Zustand wrapper around `DaemonClient` + `PiStudioClient`\n * (@av-pi-studio/client). Replaces the POC's raw `WebSocket`/`doConnect()`/`rpc()` globals\n * (POC_TO_APP_PLAN_UI.md §4.1). No raw WebSockets outside this module.\n */\n\nimport { create } from \"zustand\";\nimport {\n DaemonClient,\n PiStudioClient,\n ReconnectionManager,\n createRelayTransport,\n createWebSocketTransport,\n type ConnectionState,\n type Transport,\n} from \"@av-pi-studio/client\";\nimport type { ServerInfoPayload } from \"@av-pi-studio/protocol\";\nimport { resolveConnectTarget } from \"./resolve-connect-target.js\";\n\nexport interface ConnectOptions {\n /**\n * Daemon address, OR a full pairing link (`https://app.pi-studio.sh/#offer=...`) copied from\n * `pi-studio daemon pair`. A pairing link is detected via {@link parsePairingUrl} and switches\n * this connection to the relay transport automatically — see {@link connectViaPairingOffer}.\n * Otherwise accepts `ws://`/`wss://`, `http://`/`https://` (mapped to `ws`/`wss`), or a bare\n * `host[:port]` (assumed `ws://`), normalized via {@link normalizeDaemonUrl}.\n */\n url: string;\n /** Bearer password, sent via the `pi-studio.bearer.<pw>` WS subprotocol. Ignored for a pairing-link (relay) connection — the pairing link's public key is itself the credential. */\n password?: string;\n /** Stable per-tab client id; generated once if omitted. */\n clientId?: string;\n}\n\ninterface ConnectionStoreState {\n status: ConnectionState;\n serverInfo: ServerInfoPayload | null;\n error: string | null;\n /** Non-null once `connect()` has ever succeeded — the live SDK handles. */\n daemon: DaemonClient | null;\n client: PiStudioClient | null;\n reconnection: ReconnectionManager | null;\n\n connect(opts: ConnectOptions): Promise<void>;\n disconnect(): void;\n}\n\nfunction generateClientId(): string {\n return \"web-\" + Math.random().toString(36).slice(2, 10);\n}\n\nexport const useConnectionStore = create<ConnectionStoreState>()((set, get) => ({\n status: \"idle\",\n serverInfo: null,\n error: null,\n daemon: null,\n client: null,\n reconnection: null,\n\n async connect(opts) {\n // Tear down any previous connection before opening a new one.\n get().disconnect();\n\n // A pasted pairing link (`pi-studio daemon pair`'s QR/link) takes over connection setup\n // entirely: it carries the daemon's public key plus either a relay endpoint (branch to the\n // E2EE relay transport, ignoring `opts.password` — the pairing key IS the credential) or a\n // direct host hint (connect there exactly like a typed `url`, no password support in that\n // form since a pairing link is generated once and doesn't carry one).\n const target = resolveConnectTarget(opts.url);\n\n let transport: Transport | undefined;\n if (target.mode === \"relay\") {\n transport = createRelayTransport({ daemonPublicKey: target.daemonPublicKey });\n } else if (opts.password) {\n transport = createWebSocketTransport(\n (u) => new WebSocket(u, [`pi-studio.bearer.${opts.password}`]) as unknown as never,\n );\n }\n\n const daemon = new DaemonClient({\n url: target.url,\n clientId: opts.clientId ?? generateClientId(),\n clientType: \"browser\",\n capabilities: {},\n transport,\n // The PiStudioClient facade has no per-call timeout override for\n // createAgent/send/interrupt, and agent turns can run far longer than a\n // typical RPC (POC used a 30 min turn timeout). One rpcTimeoutMs applies\n // to every request on this connection, so size it for the longest op.\n rpcTimeoutMs: 30 * 60 * 1000,\n });\n const client = new PiStudioClient(daemon);\n const reconnection = new ReconnectionManager(daemon);\n\n daemon.onStateChange((state) => set({ status: state }));\n reconnection.onReconnected(() => set({ error: null }));\n reconnection.onReconnectFailed((error) => {\n set({ error: error instanceof Error ? error.message : String(error) });\n });\n\n set({ daemon, client, reconnection, error: null, status: \"connecting\" });\n\n try {\n const info = await daemon.connect();\n set({ serverInfo: info });\n reconnection.start();\n } catch (error) {\n set({ error: error instanceof Error ? error.message : String(error) });\n throw error;\n }\n },\n\n disconnect() {\n const { daemon, reconnection } = get();\n reconnection?.stop();\n daemon?.close();\n set({\n status: \"idle\",\n serverInfo: null,\n daemon: null,\n client: null,\n reconnection: null,\n });\n },\n}));\n\n/** Convenience selector: the live `PiStudioClient`, or `null` when disconnected. */\nexport function useClient(): PiStudioClient | null {\n return useConnectionStore((s) => s.client);\n}\n\n/** Convenience selector: current connection lifecycle state. */\nexport function useConnectionStatus(): ConnectionState {\n return useConnectionStore((s) => s.status);\n}\n","/**\n * UI store — ephemeral, session-independent UI state that doesn't belong to any single agent\n * session: the toolbar's connection fields, overlay visibility (workspace-open dialog, session\n * context menu), and `cwd` — the default cwd for a brand-new bare session and the initial path\n * of `OpenWorkspaceDialog` when no workspace is in view. NOT the \"current browsing\n * scope\" — that's `tab-store.ts`'s `activeWorkspaceCwd`, driven by whichever tab is in view\n * (§4.7 follow-up: workspace-scoped tabs). Sidebar visibility/width (collapsed flags, resizable\n * widths dragged via `ResizeHandle`) also live here. Mirrors POC globals `$(\"host\")`,\n * `$(\"password\")`, `$(\"chat-cwd\")`, `cwdPickerPath`, `menuTargetSessionId`.\n */\n\nimport { create } from \"zustand\";\n\nexport const MIN_SIDEBAR_WIDTH = 100;\nexport const MAX_SIDEBAR_WIDTH = 480;\nconst DEFAULT_LEFT_SIDEBAR_WIDTH = 220;\nconst DEFAULT_RIGHT_SIDEBAR_WIDTH = 280;\n\nfunction clampSidebarWidth(width: number): number {\n return Math.min(MAX_SIDEBAR_WIDTH, Math.max(MIN_SIDEBAR_WIDTH, width));\n}\n\nexport interface UiStoreState {\n host: string;\n password: string;\n cwd: string;\n\n cwdPickerOpen: boolean;\n sessionMenu: { sessionId: string; x: number; y: number } | null;\n fileMenu: { path: string; isDirectory: boolean; x: number; y: number } | null;\n rightSidebarTab: \"files\" | \"changes\";\n /** Workspace cwds collapsed in the sidebar tree (§4.3 workspace grouping); expanded by default. */\n collapsedWorkspaces: Set<string>;\n /** Left (sessions tree) / right (Files·Changes) sidebar visibility; both expanded by default. */\n leftSidebarCollapsed: boolean;\n rightSidebarCollapsed: boolean;\n /** User-resizable sidebar widths in px, draggable via `ResizeHandle` — clamped to [MIN_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH]. */\n leftSidebarWidth: number;\n rightSidebarWidth: number;\n\n setHost(host: string): void;\n setPassword(password: string): void;\n setCwd(cwd: string): void;\n openCwdPicker(): void;\n closeCwdPicker(): void;\n openSessionMenu(sessionId: string, x: number, y: number): void;\n closeSessionMenu(): void;\n openFileMenu(path: string, isDirectory: boolean, x: number, y: number): void;\n closeFileMenu(): void;\n setRightSidebarTab(tab: \"files\" | \"changes\"): void;\n toggleWorkspaceCollapsed(cwd: string): void;\n toggleLeftSidebar(): void;\n toggleRightSidebar(): void;\n setLeftSidebarWidth(width: number): void;\n setRightSidebarWidth(width: number): void;\n /** Delta-based resize, reading current width from store state — avoids stale-closure drift\n * across the rapid-fire `pointermove` deltas `ResizeHandle` reports. */\n resizeLeftSidebar(deltaX: number): void;\n resizeRightSidebar(deltaX: number): void;\n}\n\nexport const useUiStore = create<UiStoreState>()((set) => ({\n host: \"ws://127.0.0.1:6767\",\n password: \"\",\n cwd: \"~\",\n\n cwdPickerOpen: false,\n sessionMenu: null,\n fileMenu: null,\n rightSidebarTab: \"files\",\n collapsedWorkspaces: new Set(),\n leftSidebarCollapsed: false,\n rightSidebarCollapsed: false,\n leftSidebarWidth: DEFAULT_LEFT_SIDEBAR_WIDTH,\n rightSidebarWidth: DEFAULT_RIGHT_SIDEBAR_WIDTH,\n\n setHost: (host) => set({ host }),\n setPassword: (password) => set({ password }),\n setCwd: (cwd) => set({ cwd }),\n openCwdPicker: () => set({ cwdPickerOpen: true }),\n closeCwdPicker: () => set({ cwdPickerOpen: false }),\n openSessionMenu: (sessionId, x, y) => set({ sessionMenu: { sessionId, x, y } }),\n closeSessionMenu: () => set({ sessionMenu: null }),\n openFileMenu: (path, isDirectory, x, y) => set({ fileMenu: { path, isDirectory, x, y } }),\n closeFileMenu: () => set({ fileMenu: null }),\n setRightSidebarTab: (tab) => set({ rightSidebarTab: tab }),\n toggleWorkspaceCollapsed: (cwd) =>\n set((s) => {\n const next = new Set(s.collapsedWorkspaces);\n if (next.has(cwd)) next.delete(cwd);\n else next.add(cwd);\n return { collapsedWorkspaces: next };\n }),\n toggleLeftSidebar: () => set((s) => ({ leftSidebarCollapsed: !s.leftSidebarCollapsed })),\n toggleRightSidebar: () => set((s) => ({ rightSidebarCollapsed: !s.rightSidebarCollapsed })),\n setLeftSidebarWidth: (width) => set({ leftSidebarWidth: clampSidebarWidth(width) }),\n setRightSidebarWidth: (width) => set({ rightSidebarWidth: clampSidebarWidth(width) }),\n resizeLeftSidebar: (deltaX) =>\n set((s) => ({ leftSidebarWidth: clampSidebarWidth(s.leftSidebarWidth + deltaX) })),\n resizeRightSidebar: (deltaX) =>\n set((s) => ({ rightSidebarWidth: clampSidebarWidth(s.rightSidebarWidth + deltaX) })),\n}));\n","/**\n * Connection status dot + label (POC `#dot`/`#status`, POC_TO_APP_PLAN_UI.md §4.1).\n */\n\nimport { clsx } from \"clsx\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport styles from \"./ConnectionStatus.module.css\";\n\nconst LABEL: Record<string, string> = {\n idle: \"disconnected\",\n connecting: \"connecting…\",\n open: \"connected\",\n closing: \"disconnecting…\",\n closed: \"disconnected\",\n};\n\nexport function ConnectionStatus() {\n const status = useConnectionStore((s) => s.status);\n const error = useConnectionStore((s) => s.error);\n\n return (\n <span className={styles.wrap} title={error ?? undefined}>\n <span className={clsx(styles.dot, styles[status])} />\n {LABEL[status] ?? status}\n </span>\n );\n}\n","/**\n * Toolbar — brand, connection status, host/password inputs, connect/disconnect (POC toolbar,\n * POC_TO_APP_PLAN_UI.md §4.1). Both sidebar-visibility toggles sit together at the far right\n * (`.sidebarToggles`, `margin-left: auto`). Provider is always \"pi\" (see Composer.tsx) — this\n * project has no other provider to select. \"Open Workspace\" lives in the sidebar header\n * (SessionList.tsx), not here.\n *\n * The host field ALSO accepts a full pairing link (`pi-studio daemon pair`'s QR/link,\n * architecture/relay-e2ee.md § Pairing) pasted verbatim — `connect()` detects it via\n * `parsePairingUrl` and routes through the relay transport automatically; the password field is\n * ignored in that case (the pairing link's key is itself the credential).\n */\n\nimport { PanelLeftClose, PanelLeftOpen, PanelRightClose, PanelRightOpen } from \"lucide-react\";\nimport { Button } from \"@pi-studio-ui/components/primitives/Button.js\";\nimport { TextInput } from \"@pi-studio-ui/components/primitives/TextInput.js\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { useUiStore } from \"@pi-studio-ui/stores/ui-store.js\";\nimport { ConnectionStatus } from \"./ConnectionStatus.js\";\nimport styles from \"./Toolbar.module.css\";\n\nexport function Toolbar() {\n const status = useConnectionStore((s) => s.status);\n const connect = useConnectionStore((s) => s.connect);\n const disconnect = useConnectionStore((s) => s.disconnect);\n\n const host = useUiStore((s) => s.host);\n const setHost = useUiStore((s) => s.setHost);\n const password = useUiStore((s) => s.password);\n const setPassword = useUiStore((s) => s.setPassword);\n const leftSidebarCollapsed = useUiStore((s) => s.leftSidebarCollapsed);\n const toggleLeftSidebar = useUiStore((s) => s.toggleLeftSidebar);\n const rightSidebarCollapsed = useUiStore((s) => s.rightSidebarCollapsed);\n const toggleRightSidebar = useUiStore((s) => s.toggleRightSidebar);\n\n const connected = status === \"open\" || status === \"connecting\";\n\n return (\n <div className={styles.toolbar}>\n <span className={styles.brand}>Pi-Studio</span>\n <span className={styles.version}>v{__APP_VERSION__}</span>\n <ConnectionStatus />\n <div className={styles.sep} />\n <TextInput\n className={styles.host}\n value={host}\n onChange={(e) => setHost(e.target.value)}\n placeholder=\"host:port, ws://…/http://…, or a pasted pairing link\"\n disabled={connected}\n />\n <TextInput\n className={styles.password}\n type=\"password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n placeholder=\"password\"\n disabled={connected}\n />\n {status === \"open\" || status === \"connecting\" || status === \"closing\" ? (\n <Button size=\"sm\" onClick={() => disconnect()}>\n Disconnect\n </Button>\n ) : (\n <Button\n size=\"sm\"\n variant=\"default\"\n onClick={() => void connect({ url: host, password: password || undefined })}\n >\n Connect\n </Button>\n )}\n <div className={styles.sidebarToggles}>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n iconOnly\n title={leftSidebarCollapsed ? \"Show sessions sidebar\" : \"Hide sessions sidebar\"}\n onClick={() => toggleLeftSidebar()}\n >\n {leftSidebarCollapsed ? <PanelLeftOpen size={16} /> : <PanelLeftClose size={16} />}\n </Button>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n iconOnly\n title={rightSidebarCollapsed ? \"Show files/changes sidebar\" : \"Hide files/changes sidebar\"}\n onClick={() => toggleRightSidebar()}\n >\n {rightSidebarCollapsed ? <PanelRightOpen size={16} /> : <PanelRightClose size={16} />}\n </Button>\n </div>\n </div>\n );\n}\n","/**\n * TimelineRow — the render model streamed/replayed chat rows collapse into.\n * Discriminated union mirrors the POC's per-kind DOM append logic\n * (`handleAgentStream`, POC_TO_APP_PLAN_UI.md §4.3) without the DOM.\n */\n\nimport type { ToolCallDetail } from \"@av-pi-studio/protocol\";\n\nexport type ToolCallStatus = \"running\" | \"completed\" | \"error\";\n\nexport interface UserRow {\n kind: \"user\";\n id: string;\n text: string;\n images?: Array<{ mimeType?: string; data?: string }>;\n /**\n * Client-generated id passed as `clientMessageId` on `createAgent`/`send` (features/\n * agent-sessions.md — the daemon echoes it back verbatim as the canonical `user_message`\n * event's `messageId` since the live pi provider never mints its own). Lets the optimistic\n * row inserted on Send reconcile against the server's broadcast instead of duplicating it.\n * Undefined for rows sourced from session-restore hydration/replay, which had no local\n * optimistic counterpart to reconcile against.\n */\n clientMessageId?: string;\n /**\n * True from the moment `Composer` inserts this row locally until the server's `user_message`\n * broadcast for the same `clientMessageId` confirms it (see `reducer.ts`'s `onUserMessage`).\n * Hydrated/replayed rows are never pending — they are already server-confirmed history.\n */\n pending?: boolean;\n /**\n * True when the RPC that was supposed to confirm this row (`createAgent`/`send`) rejected\n * before any `user_message` broadcast arrived — e.g. a dropped connection. Never confirmed,\n * never retried automatically; `pending` is cleared alongside it (see `reducer.ts`'s\n * `markUserMessageFailed`) so the row stops showing a spinner and shows a failure indicator\n * instead.\n */\n failed?: boolean;\n /**\n * True from the moment a steered message is optimistically inserted (`Composer`'s Steer path,\n * `clientMessageId`/`queued: true` on `addOptimisticUserMessage`) until a `queue_update` stream\n * event no longer lists this row's `text` in its `steering[]` array — i.e. Pi has handed the\n * steering message to the LLM. Best-effort text correlation (`queue_update` carries strings, not\n * ids — see `reducer.ts`'s `onQueueUpdate`). Never set on a normal (non-steered) send.\n */\n queued?: boolean;\n}\n\nexport interface AssistantRow {\n kind: \"assistant\";\n id: string;\n text: string;\n /** True while more `assistant_message` deltas for this turn may still arrive. */\n streaming: boolean;\n}\n\nexport interface ReasoningRow {\n kind: \"reasoning\";\n id: string;\n text: string;\n streaming: boolean;\n}\n\nexport interface ToolRow {\n kind: \"tool\";\n id: string;\n /** Stable key used to upsert/dedupe: `callId` when present, else `${tool.kind}:${detail}`. */\n callId: string;\n tool: ToolCallDetail;\n status: ToolCallStatus;\n}\n\nexport interface ErrorRow {\n kind: \"error\";\n id: string;\n text: string;\n}\n\nexport interface SystemRow {\n kind: \"system\";\n id: string;\n text: string;\n}\n\nexport type TimelineRow = UserRow | AssistantRow | ReasoningRow | ToolRow | ErrorRow | SystemRow;\n\nexport interface TimelineState {\n rows: TimelineRow[];\n /** Index into `rows` of the in-progress assistant row, if any (fast tail lookup). */\n streamingAssistantIndex: number | null;\n /** Index into `rows` of the in-progress reasoning row, if any. */\n streamingReasoningIndex: number | null;\n /** callId → row index, for O(1) tool-call upsert. */\n toolIndexByCallId: Record<string, number>;\n}\n\nexport const EMPTY_TIMELINE: TimelineState = {\n rows: [],\n streamingAssistantIndex: null,\n streamingReasoningIndex: null,\n toolIndexByCallId: {},\n};\n\nlet rowSeq = 0;\nexport function nextRowId(): string {\n rowSeq += 1;\n return `row-${rowSeq}`;\n}\n\n/** Detail text extracted from a tool-call for display (POC `t.command || t.path || …`). */\nexport function toolDetailText(tool: ToolCallDetail): string {\n switch (tool.kind) {\n case \"shell\":\n return tool.command ?? \"\";\n case \"read\":\n case \"write\":\n return tool.path ?? \"\";\n case \"edit\":\n return tool.path ?? \"\";\n case \"search\":\n return tool.query ?? \"\";\n case \"fetch\":\n return tool.url ?? \"\";\n case \"task\":\n return tool.description ?? \"\";\n default:\n return \"\";\n }\n}\n\n/** Stable dedupe key for a tool call (POC `e.callId || (t.kind + \":\" + detail)`). */\nexport function toolCallKey(callId: string | undefined, tool: ToolCallDetail): string {\n return callId ?? `${tool.kind}:${toolDetailText(tool)}`;\n}\n","/**\n * Pure `(TimelineState, AgentStreamEvent) → TimelineState` reducer. Replaces the POC's\n * imperative `handleAgentStream` DOM switch (POC_TO_APP_PLAN_UI.md §4.3). No DOM, no globals —\n * fully unit-testable.\n */\n\nimport type { AgentStreamEvent, ToolCallDetail } from \"@av-pi-studio/protocol\";\nimport {\n EMPTY_TIMELINE,\n nextRowId,\n toolCallKey,\n type TimelineRow,\n type TimelineState,\n} from \"./row-model.js\";\n\n/** Reset streaming refs for a new turn (POC: `turn_started` clears `currentBubble`/`currentReasoning`/`toolEls`). */\nfunction onTurnStarted(state: TimelineState): TimelineState {\n return {\n ...state,\n streamingAssistantIndex: null,\n streamingReasoningIndex: null,\n // toolIndexByCallId intentionally persists across turns — POC only clears `st.toolEls` on\n // `turn_started`, but tool rows themselves remain in the DOM; dedupe-by-callId is scoped to\n // the whole session, not the turn, and callIds are unique per invocation anyway.\n };\n}\n\nfunction onAssistantMessage(state: TimelineState, text: string): TimelineState {\n const rows = state.rows.slice();\n let index = state.streamingAssistantIndex;\n if (index === null || rows[index]?.kind !== \"assistant\") {\n const row: TimelineRow = { kind: \"assistant\", id: nextRowId(), text: \"\", streaming: true };\n rows.push(row);\n index = rows.length - 1;\n }\n const row = rows[index];\n if (!row || row.kind !== \"assistant\") return state; // unreachable given the guard above\n rows[index] = { ...row, text: row.text + text };\n return {\n ...state,\n rows,\n streamingAssistantIndex: index,\n streamingReasoningIndex: null, // POC: assistant_message clears currentReasoning\n };\n}\n\nfunction onReasoning(state: TimelineState, text: string): TimelineState {\n const rows = state.rows.slice();\n let index = state.streamingReasoningIndex;\n if (index === null || rows[index]?.kind !== \"reasoning\") {\n const row: TimelineRow = { kind: \"reasoning\", id: nextRowId(), text: \"\", streaming: true };\n rows.push(row);\n index = rows.length - 1;\n }\n const row = rows[index];\n if (!row || row.kind !== \"reasoning\") return state;\n rows[index] = { ...row, text: row.text + text };\n return {\n ...state,\n rows,\n streamingReasoningIndex: index,\n streamingAssistantIndex: null, // POC: reasoning clears currentBubble\n };\n}\n\n/**\n * Merge tool detail across upserts for the same callId. Pi splits detail across events: the\n * command/path arrive on `tool_execution_start` (→ status \"running\") while the edit diff and the\n * command's output arrive only on `tool_execution_end` (→ \"completed\"/\"error\"), which carries no\n * args. Overwriting wholesale would blank the command/path when the end event lands, so we keep\n * each existing non-empty field unless the incoming event provides a value for it. Same `kind`\n * always (both events describe the same tool), so the union stays well-formed.\n */\nfunction mergeTool(prev: ToolCallDetail, next: ToolCallDetail): ToolCallDetail {\n if (prev.kind !== next.kind) return next;\n const merged: Record<string, unknown> = { ...prev };\n for (const [key, value] of Object.entries(next)) {\n if (value !== undefined && value !== \"\") merged[key] = value;\n }\n return merged as ToolCallDetail;\n}\n\nfunction onToolCall(\n state: TimelineState,\n event: Extract<AgentStreamEvent, { kind: \"tool_call\" }>,\n): TimelineState {\n const key = toolCallKey(event.callId, event.tool);\n const status = event.status === \"completed\" || event.status === \"error\" ? event.status : \"running\";\n const existingIndex = state.toolIndexByCallId[key];\n const rows = state.rows.slice();\n\n if (existingIndex !== undefined && rows[existingIndex]?.kind === \"tool\") {\n const row = rows[existingIndex];\n if (row.kind !== \"tool\") return state;\n rows[existingIndex] = { ...row, tool: mergeTool(row.tool, event.tool), status };\n return {\n ...state,\n rows,\n streamingAssistantIndex: null,\n streamingReasoningIndex: null,\n };\n }\n\n const row: TimelineRow = { kind: \"tool\", id: nextRowId(), callId: key, tool: event.tool, status };\n rows.push(row);\n return {\n ...state,\n rows,\n toolIndexByCallId: { ...state.toolIndexByCallId, [key]: rows.length - 1 },\n streamingAssistantIndex: null,\n streamingReasoningIndex: null,\n };\n}\n\nfunction onTurnCompleted(state: TimelineState): TimelineState {\n const rows = state.rows.slice();\n if (state.streamingAssistantIndex !== null) {\n const row = rows[state.streamingAssistantIndex];\n if (row?.kind === \"assistant\") rows[state.streamingAssistantIndex] = { ...row, streaming: false };\n }\n if (state.streamingReasoningIndex !== null) {\n const row = rows[state.streamingReasoningIndex];\n if (row?.kind === \"reasoning\") rows[state.streamingReasoningIndex] = { ...row, streaming: false };\n }\n return { ...state, rows, streamingAssistantIndex: null, streamingReasoningIndex: null };\n}\n\nfunction onTurnFailed(state: TimelineState, error: string | undefined): TimelineState {\n const finalized = onTurnCompleted(state);\n const row: TimelineRow = { kind: \"error\", id: nextRowId(), text: error || \"turn failed\" };\n return { ...finalized, rows: [...finalized.rows, row] };\n}\n\nfunction onTurnCanceled(state: TimelineState): TimelineState {\n const finalized = onTurnCompleted(state);\n const row: TimelineRow = { kind: \"system\", id: nextRowId(), text: \"(canceled)\" };\n return { ...finalized, rows: [...finalized.rows, row] };\n}\n\nfunction onError(state: TimelineState, message: string | undefined): TimelineState {\n const row: TimelineRow = { kind: \"error\", id: nextRowId(), text: message || \"error\" };\n return { ...state, rows: [...state.rows, row] };\n}\n\nfunction onUserMessage(\n state: TimelineState,\n text: string,\n images?: Array<{ mimeType?: string; data?: string }>,\n messageId?: string,\n): TimelineState {\n // Reconcile against the optimistic row `Composer` inserted synchronously on Send (same\n // `clientMessageId`, echoed back verbatim by the daemon as this event's `messageId` — see\n // `packages/server/src/agent/agent-service.ts` `runTurn`): confirm it in place instead of\n // appending a duplicate. No match (session-restore replay, or a `messageId` the daemon minted\n // itself) → append fresh, already-confirmed.\n if (messageId !== undefined) {\n const idx = state.rows.findIndex(\n (r) => r.kind === \"user\" && r.pending && r.clientMessageId === messageId,\n );\n if (idx !== -1) {\n const rows = state.rows.slice();\n const prev = rows[idx];\n if (prev?.kind === \"user\") {\n rows[idx] = { ...prev, text, images, pending: false };\n return { ...state, rows };\n }\n }\n }\n const row: TimelineRow = { kind: \"user\", id: nextRowId(), text, images, clientMessageId: messageId };\n return { ...state, rows: [...state.rows, row] };\n}\n\n/**\n * `queue_update` carries the pending-steering queue as raw strings, not ids (Pi's `steer`/\n * `follow_up` RPC has no message-id concept — see `provider-contract.ts`). Clear the `queued` flag\n * on any row whose text no longer appears in `steering[]`: Pi has handed it to the LLM. Rows are\n * matched by exact text, best-effort — sufficient for a UX badge, not a precise queue model.\n */\nfunction onQueueUpdate(state: TimelineState, steering: readonly string[]): TimelineState {\n let changed = false;\n const rows = state.rows.map((row) => {\n if (row.kind !== \"user\" || !row.queued) return row;\n if (steering.includes(row.text)) return row;\n changed = true;\n return { ...row, queued: false };\n });\n return changed ? { ...state, rows } : state;\n}\n\n/** Apply one `AgentStreamEvent` to timeline state. Pure — no mutation of the input. */\nexport function applyStreamEvent(state: TimelineState, event: AgentStreamEvent): TimelineState {\n switch (event.kind) {\n case \"turn_started\":\n return onTurnStarted(state);\n case \"user_message\":\n return onUserMessage(state, event.text ?? \"\", event.images, event.messageId);\n case \"assistant_message\":\n return onAssistantMessage(state, event.text ?? \"\");\n case \"reasoning\":\n return onReasoning(state, event.text ?? \"\");\n case \"tool_call\":\n return onToolCall(state, event);\n case \"turn_completed\":\n return onTurnCompleted(state);\n case \"turn_failed\":\n return onTurnFailed(state, event.error);\n case \"turn_canceled\":\n return onTurnCanceled(state);\n case \"error\":\n return onError(state, event.message);\n case \"queue_update\":\n return onQueueUpdate(state, event.steering ?? []);\n default:\n return state; // unknown/future kind — ignore gracefully (append-only protocol)\n }\n}\n\n/** Replay a whole ordered event list (session restore / timeline hydration) into fresh state. */\nexport function replayEvents(events: readonly AgentStreamEvent[]): TimelineState {\n return events.reduce(applyStreamEvent, EMPTY_TIMELINE);\n}\n\n/**\n * Insert the local optimistic echo `Composer` renders synchronously on Send/Steer, before the\n * daemon's `create_agent_request`/`send_agent_prompt`/`steer_agent_request` round trip resolves.\n * Keyed by the same `clientMessageId` sent on the RPC — the eventual `user_message` broadcast\n * (`onUserMessage` above) reconciles this row in place by matching `pending` + `clientMessageId`,\n * so the row never duplicates once the server confirms it. `queued: true` marks a steered message\n * (cleared later by `onQueueUpdate` above); a normal send never sets it.\n */\nexport function addOptimisticUserMessage(\n state: TimelineState,\n clientMessageId: string,\n text: string,\n images?: Array<{ mimeType?: string; data?: string }>,\n queued?: boolean,\n): TimelineState {\n const row: TimelineRow = {\n kind: \"user\",\n id: nextRowId(),\n text,\n images,\n clientMessageId,\n pending: true,\n queued,\n };\n return { ...state, rows: [...state.rows, row] };\n}\n\n/**\n * Mark a still-pending optimistic row as failed — the RPC that was meant to confirm it\n * (`createAgent`/`send`) rejected before any `user_message` broadcast arrived. No-op if the row\n * was already reconciled (server confirmed it before the RPC promise settled — the RPC contract\n * is \"resolves after the whole turn\", so a `user_message` broadcast can arrive, then the promise\n * can still reject later from a failure elsewhere in that same turn; the row is already correct\n * by then and must not be clobbered back into a failed state).\n */\nexport function markUserMessageFailed(state: TimelineState, clientMessageId: string): TimelineState {\n const idx = state.rows.findIndex(\n (r) => r.kind === \"user\" && r.pending && r.clientMessageId === clientMessageId,\n );\n if (idx === -1) return state;\n const rows = state.rows.slice();\n const prev = rows[idx];\n if (prev?.kind !== \"user\") return state;\n rows[idx] = { ...prev, pending: false, failed: true };\n return { ...state, rows };\n}\n\nexport { EMPTY_TIMELINE };\nexport type { TimelineState, TimelineRow };\n","/**\n * Session store — replaces the POC's `sessions[]`/`activeSessionId` globals\n * (POC_TO_APP_PLAN_UI.md §4.3). Kills the POC's `expectingAgent` race: a session only subscribes\n * to `agent(id).timeline.subscribe` once `agentId` is bound (from the `createAgent` response),\n * so no event can arrive before the session that owns it is known — no guessing required.\n */\n\nimport { create } from \"zustand\";\nimport type { AgentStatus } from \"@av-pi-studio/protocol\";\nimport {\n EMPTY_TIMELINE,\n applyStreamEvent as applyStreamEventToTimeline,\n addOptimisticUserMessage as addOptimisticUserMessageToTimeline,\n markUserMessageFailed as markUserMessageFailedInTimeline,\n} from \"@pi-studio-ui/timeline/reducer.js\";\nimport type { TimelineState } from \"@pi-studio-ui/timeline/reducer.js\";\nimport type { AgentStreamEvent } from \"@av-pi-studio/protocol\";\n\nexport interface SessionEntry {\n id: string;\n agentId: string | null;\n title: string;\n status: AgentStatus | \"idle\";\n cwd: string;\n timeline: TimelineState;\n /** User-turn count, for the sidebar meta line (POC: `messages.filter(role==='user').length`). */\n userMessageCount: number;\n /** Current model id, shown by the workspace status bar's model segment (sprint-042). Seeded\n * from the preselected default (`materialize.ts` `resolveDefaultModel`) or create-agent config\n * / a restored `list_agents` entry, updated live by an `agent_update({model})` broadcast, and\n * reconciled by the periodic session-stats poll. */\n model?: string;\n /** The current model's own underlying LLM provider (e.g. `\"anthropic\"`) — travels alongside\n * `model` so a materialized draft can pin `config.modelProvider` for first-spawn replay\n * (`agent-service.ts` `spawnOrResumeSession`) without a second round trip to re-derive it. */\n modelProvider?: string;\n}\n\ninterface SessionStoreState {\n sessions: Record<string, SessionEntry>;\n order: string[];\n activeSessionId: string | null;\n\n createSession(cwd: string): string;\n bindAgent(sessionId: string, agentId: string): void;\n setStatus(sessionId: string, status: AgentStatus | \"idle\"): void;\n setStatusByAgentId(agentId: string, status: AgentStatus | \"idle\"): void;\n setModel(sessionId: string, model: string | undefined, modelProvider?: string): void;\n setModelByAgentId(agentId: string, model: string | undefined, modelProvider?: string): void;\n setTitle(sessionId: string, title: string): void;\n setCwd(sessionId: string, cwd: string): void;\n applyStreamEvent(sessionId: string, event: AgentStreamEvent): void;\n addOptimisticUserMessage(\n sessionId: string,\n clientMessageId: string,\n text: string,\n images?: Array<{ mimeType?: string; data?: string }>,\n queued?: boolean,\n ): void;\n markUserMessageFailed(sessionId: string, clientMessageId: string): void;\n activate(sessionId: string): void;\n remove(sessionId: string): void;\n /** Register a restored session (session restore on connect, §4.3 \"Session restore\"). */\n hydrate(entry: SessionEntry): void;\n findByAgentId(agentId: string): SessionEntry | undefined;\n}\n\nlet sessionSeq = 0;\nfunction newSessionId(): string {\n sessionSeq += 1;\n return `s-${Date.now().toString(36)}-${sessionSeq}`;\n}\n\nexport const useSessionStore = create<SessionStoreState>()((set, get) => ({\n sessions: {},\n order: [],\n activeSessionId: null,\n\n createSession(cwd) {\n const id = newSessionId();\n const entry: SessionEntry = {\n id,\n agentId: null,\n title: \"New chat\",\n status: \"idle\",\n cwd,\n timeline: EMPTY_TIMELINE,\n userMessageCount: 0,\n };\n set((s) => ({\n sessions: { ...s.sessions, [id]: entry },\n order: [id, ...s.order],\n activeSessionId: id,\n }));\n return id;\n },\n\n hydrate(entry) {\n set((s) => {\n if (s.sessions[entry.id]) return s; // already known — don't clobber live state\n return {\n sessions: { ...s.sessions, [entry.id]: entry },\n order: [...s.order, entry.id],\n };\n });\n },\n\n bindAgent(sessionId, agentId) {\n set((s) => {\n const entry = s.sessions[sessionId];\n if (!entry) return s;\n return { sessions: { ...s.sessions, [sessionId]: { ...entry, agentId } } };\n });\n },\n\n setStatus(sessionId, status) {\n set((s) => {\n const entry = s.sessions[sessionId];\n if (!entry) return s;\n return { sessions: { ...s.sessions, [sessionId]: { ...entry, status } } };\n });\n },\n\n setStatusByAgentId(agentId, status) {\n const entry = get().findByAgentId(agentId);\n if (entry) get().setStatus(entry.id, status);\n },\n\n setModel(sessionId, model, modelProvider) {\n set((s) => {\n const entry = s.sessions[sessionId];\n if (!entry) return s;\n return { sessions: { ...s.sessions, [sessionId]: { ...entry, model, modelProvider } } };\n });\n },\n\n setModelByAgentId(agentId, model, modelProvider) {\n const entry = get().findByAgentId(agentId);\n if (entry) get().setModel(entry.id, model, modelProvider);\n },\n\n setTitle(sessionId, title) {\n set((s) => {\n const entry = s.sessions[sessionId];\n if (!entry) return s;\n return { sessions: { ...s.sessions, [sessionId]: { ...entry, title } } };\n });\n },\n\n setCwd(sessionId, cwd) {\n set((s) => {\n const entry = s.sessions[sessionId];\n if (!entry) return s;\n return { sessions: { ...s.sessions, [sessionId]: { ...entry, cwd } } };\n });\n },\n\n applyStreamEvent(sessionId, event) {\n set((s) => {\n const entry = s.sessions[sessionId];\n if (!entry) return s;\n const timeline = applyStreamEventToTimeline(entry.timeline, event);\n let title = entry.title;\n if (event.kind === \"turn_completed\" && title === \"New chat\") {\n const lastAssistant = timeline.rows\n .slice()\n .reverse()\n .find((r) => r.kind === \"assistant\");\n if (lastAssistant && lastAssistant.kind === \"assistant\" && lastAssistant.text) {\n title = lastAssistant.text.slice(0, 40) + (lastAssistant.text.length > 40 ? \"…\" : \"\");\n }\n }\n return { sessions: { ...s.sessions, [sessionId]: { ...entry, timeline, title } } };\n });\n },\n\n addOptimisticUserMessage(sessionId, clientMessageId, text, images, queued) {\n set((s) => {\n const entry = s.sessions[sessionId];\n if (!entry) return s;\n const timeline = addOptimisticUserMessageToTimeline(\n entry.timeline,\n clientMessageId,\n text,\n images,\n queued,\n );\n return {\n sessions: {\n ...s.sessions,\n [sessionId]: { ...entry, timeline, userMessageCount: entry.userMessageCount + 1 },\n },\n };\n });\n },\n\n markUserMessageFailed(sessionId, clientMessageId) {\n set((s) => {\n const entry = s.sessions[sessionId];\n if (!entry) return s;\n const timeline = markUserMessageFailedInTimeline(entry.timeline, clientMessageId);\n return { sessions: { ...s.sessions, [sessionId]: { ...entry, timeline } } };\n });\n },\n\n activate(sessionId) {\n set({ activeSessionId: sessionId });\n },\n\n remove(sessionId) {\n set((s) => {\n const { [sessionId]: _removed, ...rest } = s.sessions;\n const order = s.order.filter((id) => id !== sessionId);\n const activeSessionId = s.activeSessionId === sessionId ? order[0] ?? null : s.activeSessionId;\n return { sessions: rest, order, activeSessionId };\n });\n },\n\n findByAgentId(agentId) {\n return Object.values(get().sessions).find((s) => s.agentId === agentId);\n },\n}));\n","/**\n * Deferred-draft materialization + preselected default model — the client-side half of the\n * server's deferred-spawn draft creation (`agent-service.ts` `handleCreate`/\n * `spawnOrResumeSession`). Opening a \"New chat\" tab creates only a client-only `SessionEntry`\n * (`session-store.ts` `createSession`, `agentId: null`) — nothing is persisted or spawned\n * server-side until `tab-store.ts` `openNewChat` eagerly calls `ensureMaterialized` the moment the\n * tab is created, which commits it into a real, restorable `AgentRecord` without spawning a `pi`\n * process (that stays deferred to the first send). `ensureMaterialized` also resolves the default\n * model itself when the entry doesn't have one yet, so callers never need a separate display-only\n * lookup before materializing.\n */\n\nimport type { PiStudioClient } from \"@av-pi-studio/client\";\nimport { useSessionStore } from \"./session-store.js\";\n\nexport interface DefaultModel {\n model?: string;\n modelProvider?: string;\n}\n\n/**\n * Cached per connection (`WeakMap` keyed by the live `PiStudioClient` instance — a reconnect gets\n * a new client and therefore a fresh cache, exactly matching the daemon-side cache's \"per\n * process\" scope). The first call after a connection opens pays one transient `--no-session`\n * spawn (`resolve_default_model`, `providers/pi/agent.ts`); every subsequent \"New chat\" on the\n * same connection is instant. A failed lookup (e.g. the provider can't resolve one) is cached too\n * — an empty result just means the composer shows its placeholder, so retrying on every keystroke\n * would be pure waste.\n */\nconst defaultModelCache = new WeakMap<PiStudioClient, Promise<DefaultModel>>();\n\n/** Resolve the model a brand-new chat would run on with no override — settings' configured\n * default, else the provider's built-in default — WITHOUT spawning anything. Called by\n * `ensureMaterialized` itself when an entry has no model yet; exported separately only because\n * cache scope (per `PiStudioClient` instance) is useful to assert on directly in tests. */\nexport function resolveDefaultModel(client: PiStudioClient, provider = \"pi\"): Promise<DefaultModel> {\n const cached = defaultModelCache.get(client);\n if (cached) return cached;\n const promise = client.providers\n .resolveDefaultModel(provider)\n .then((res) => ({ model: res.model, modelProvider: res.modelProvider }))\n .catch(() => ({}) as DefaultModel);\n defaultModelCache.set(client, promise);\n return promise;\n}\n\n/** In-flight materializations, keyed by `sessionId` — serializes concurrent callers (eager-open\n * racing an explicit model pick racing Send) onto the same `createAgent` call instead of each\n * firing its own and racing to `bindAgent`. */\nconst materializing = new Map<string, Promise<string>>();\n\n/**\n * Ensure `sessionId` has a bound `agentId`, materializing a deferred draft if it doesn't yet.\n * Materializing means `createAgent` with NO `initialPrompt` — the server persists the record but\n * does not spawn a provider process (`agent-service.ts` `handleCreate`'s deferred-draft branch) —\n * carrying whatever model is CURRENTLY on the session entry (the preselected default from\n * `resolveDefaultModel`, or an explicit `/model` pick; either way, `config.model`/\n * `config.modelProvider` is what the server replays on first spawn, unconditionally, since\n * neither `createSession` nor `resumeSession` consult it themselves).\n *\n * Idempotent: returns the already-bound `agentId` immediately, or the same in-flight promise if\n * a materialize is already running for this session.\n */\nexport function ensureMaterialized(client: PiStudioClient, sessionId: string): Promise<string> {\n const entry = useSessionStore.getState().sessions[sessionId];\n if (!entry) return Promise.reject(new Error(`unknown session: ${sessionId}`));\n if (entry.agentId) return Promise.resolve(entry.agentId);\n\n const inFlight = materializing.get(sessionId);\n if (inFlight) return inFlight;\n\n const promise = (async () => {\n // Seed the default model before building `config` — `spawnOrResumeSession` reads\n // `record.config.model` at spawn time, so this must land in the SAME `createAgent` call\n // rather than a follow-up `setModel` (see module header). Skipped once the entry already has\n // a model, whether from an earlier preselect or an explicit pick — and re-checked against the\n // CURRENT entry (not the stale `entry` closure) after the lookup resolves, so an explicit\n // pick that lands while the lookup is in flight always wins over the resolved default.\n if (!entry.model) {\n const resolved = await resolveDefaultModel(client);\n const current = useSessionStore.getState().sessions[sessionId];\n if (resolved.model && current && !current.model) {\n useSessionStore.getState().setModel(sessionId, resolved.model, resolved.modelProvider);\n }\n }\n // Re-read: an explicit pick or a `remove` may have landed while the lookup above was in\n // flight (a no-op re-read when no await happened above, since nothing else runs between two\n // synchronous statements).\n const fresh = useSessionStore.getState().sessions[sessionId];\n if (!fresh) throw new Error(`unknown session: ${sessionId}`);\n\n const config: { provider: string; cwd: string; model?: string; modelProvider?: string } = {\n provider: \"pi\",\n cwd: fresh.cwd || \"~\",\n };\n if (fresh.model) config.model = fresh.model;\n if (fresh.modelProvider) config.modelProvider = fresh.modelProvider;\n const result = await client.createAgent({ config, labels: {} });\n useSessionStore.getState().bindAgent(sessionId, result.agentId);\n return result.agentId;\n })();\n\n materializing.set(sessionId, promise);\n void promise.finally(() => materializing.delete(sessionId));\n return promise;\n}\n\n/** Discard a chat whose conversation never started: closing its tab throws it away instead of\n * leaving an empty persisted record behind forever. A record that's never closed persists\n * indefinitely by design (`use-session-restore.ts` hydrates every known agent unconditionally,\n * including an untouched draft) — this is the ONLY path that removes one short of an explicit\n * delete. A chat with any timeline row is kept (closing the tab keeps the session, as before).\n * Returns whether the session was removed, purely for tests — callers (`tab-store.ts`\n * `closeTab`) treat this as fire-and-forget. */\nexport async function discardIfEmpty(\n client: PiStudioClient | null,\n sessionId: string,\n): Promise<boolean> {\n const entry = useSessionStore.getState().sessions[sessionId];\n if (!entry) return false;\n if (entry.userMessageCount > 0 || entry.timeline.rows.length > 0) return false;\n\n // An eager materialize (`tab-store.ts` `openNewChat`) may still be in flight — await it so the\n // `agentId` it is about to bind isn't leaked (created, then never deleted because `entry`\n // was read before the bind landed).\n await materializing.get(sessionId)?.catch(() => {});\n const fresh = useSessionStore.getState().sessions[sessionId];\n if (!fresh) return false;\n\n if (fresh.agentId && client) {\n await client\n .agent(fresh.agentId)\n .delete()\n .catch(() => {\n // Best-effort hard delete: the local entry goes away regardless (mirrors\n // `SessionContextMenu.remove`'s convention), even if the RPC itself failed.\n });\n }\n useSessionStore.getState().remove(sessionId);\n return true;\n}\n","/**\n * Tab store — replaces the POC's `tabs[]`/`activeTabId` globals + `addTab`/`closeTab`/\n * `activateTab` (POC_TO_APP_PLAN_UI.md §4.2). `TabPanelHost` keeps inactive panels mounted but\n * hidden, so this store only tracks identity/order/label — not panel content.\n *\n * Tabs are scoped by workspace (§4.3/§4.7 follow-up: workspace-scoped tabs): every `Tab` carries\n * the (already `normalizeCwd`-normalized) workspace cwd it belongs to, and `activeWorkspaceCwd`\n * names which workspace's tabs `TabStrip`/`TabPanelHost` currently render. All tabs stay mounted\n * regardless of workspace — same \"hidden but alive\" model that already preserves terminal/scroll\n * state across in-workspace tab switches — only the visible SUBSET changes on a workspace switch.\n * `lastActiveTabByWorkspace` remembers each workspace's last-focused tab so switching back to a\n * workspace restores exactly where you left it, not always its first tab.\n */\n\nimport { create } from \"zustand\";\nimport { useSessionStore } from \"@pi-studio-ui/stores/session-store.js\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { ensureMaterialized, discardIfEmpty } from \"@pi-studio-ui/stores/materialize.js\";\n\nexport type TabKind = \"chat\" | \"file\" | \"diff\" | \"terminal\";\n\nexport interface FileTabData {\n path: string;\n}\n\nexport interface DiffTabData {\n path: string;\n staged: boolean;\n}\n\nexport interface ChatTabData {\n sessionId: string;\n}\n\nexport interface TerminalTabData {\n slot: number | null;\n /** Cwd captured at shortcut-fire time — the terminal launches here regardless of which\n * session/workspace becomes active before its `create_terminal_request` resolves. */\n cwd: string;\n}\n\nexport type TabData = ChatTabData | FileTabData | DiffTabData | TerminalTabData;\n\nexport interface Tab {\n id: string;\n kind: TabKind;\n label: string;\n closable: boolean;\n data: TabData;\n /** Normalized workspace cwd this tab belongs to (see `workspace-grouping.ts#normalizeCwd`) —\n * fixed at creation time, never re-derived live from whatever session happens to be active. */\n workspaceCwd: string;\n}\n\ninterface TabStoreState {\n tabs: Tab[];\n activeTabId: string | null;\n /** Which workspace's tabs are currently visible in the strip/panel host. */\n activeWorkspaceCwd: string | null;\n /** Last-focused tab id per workspace cwd, for restore-on-switch. */\n lastActiveTabByWorkspace: Record<string, string>;\n\n /** Open a tab, or activate it if a tab with the same `id` already exists (POC `addTab`).\n * Always brings the tab's workspace into view. */\n open(tab: Tab): void;\n close(id: string): void;\n /** Activate a tab by id, bringing its workspace into view too. */\n activate(id: string): void;\n /** Switch the visible workspace, restoring its last-focused tab (or its first tab, or none). */\n switchWorkspace(cwd: string): void;\n reorder(fromId: string, toId: string): void;\n updateLabel(id: string, label: string): void;\n updateData(id: string, data: Partial<TabData>): void;\n}\n\nfunction tabsInWorkspace(tabs: Tab[], cwd: string): Tab[] {\n return tabs.filter((t) => t.workspaceCwd === cwd);\n}\n\n/** Keep the sidebar's `session-store.activeSessionId` in lockstep with whichever chat tab is\n * now active, so switching tabs in the center pane highlights the matching sidebar row exactly\n * as if it had been clicked there directly. No-op for non-chat tabs (file/diff/terminal) — the\n * sidebar simply keeps showing whichever chat was last active. */\nfunction syncActiveSession(tab: Tab | undefined): void {\n if (tab?.kind !== \"chat\") return;\n useSessionStore.getState().activate((tab.data as ChatTabData).sessionId);\n}\n\nexport const useTabStore = create<TabStoreState>()((set, get) => ({\n tabs: [],\n activeTabId: null,\n activeWorkspaceCwd: null,\n lastActiveTabByWorkspace: {},\n\n open(tab) {\n const existing = get().tabs.find((t) => t.id === tab.id);\n if (existing) {\n get().activate(existing.id);\n return;\n }\n set((s) => ({\n tabs: [...s.tabs, tab],\n activeTabId: tab.id,\n activeWorkspaceCwd: tab.workspaceCwd,\n lastActiveTabByWorkspace: { ...s.lastActiveTabByWorkspace, [tab.workspaceCwd]: tab.id },\n }));\n syncActiveSession(tab);\n },\n\n close(id) {\n let fallbackTab: Tab | undefined;\n set((s) => {\n const closed = s.tabs.find((t) => t.id === id);\n if (!closed) return s;\n const idx = s.tabs.findIndex((t) => t.id === id);\n const tabs = s.tabs.filter((t) => t.id !== id);\n\n let activeTabId = s.activeTabId;\n const lastActiveTabByWorkspace = { ...s.lastActiveTabByWorkspace };\n if (activeTabId === id) {\n // Fall back to a sibling within the SAME workspace, not the global next tab — closing a\n // tab must never jump the visible workspace out from under the user.\n const siblings = tabsInWorkspace(tabs, closed.workspaceCwd);\n const siblingIdx = Math.min(idx, siblings.length - 1);\n const next = siblings[siblingIdx];\n activeTabId = next?.id ?? null;\n fallbackTab = next;\n }\n if (lastActiveTabByWorkspace[closed.workspaceCwd] === id) {\n const stillOpen = tabsInWorkspace(tabs, closed.workspaceCwd)[0];\n if (stillOpen) lastActiveTabByWorkspace[closed.workspaceCwd] = stillOpen.id;\n else delete lastActiveTabByWorkspace[closed.workspaceCwd];\n }\n return { tabs, activeTabId, lastActiveTabByWorkspace };\n });\n syncActiveSession(fallbackTab);\n },\n\n activate(id) {\n let activatedTab: Tab | undefined;\n set((s) => {\n const tab = s.tabs.find((t) => t.id === id);\n if (!tab) return s;\n activatedTab = tab;\n return {\n activeTabId: id,\n activeWorkspaceCwd: tab.workspaceCwd,\n lastActiveTabByWorkspace: { ...s.lastActiveTabByWorkspace, [tab.workspaceCwd]: id },\n };\n });\n syncActiveSession(activatedTab);\n },\n\n switchWorkspace(cwd) {\n let switchedTab: Tab | undefined;\n set((s) => {\n const siblings = tabsInWorkspace(s.tabs, cwd);\n const remembered = s.lastActiveTabByWorkspace[cwd];\n const stillOpen = remembered && siblings.some((t) => t.id === remembered);\n const activeTabId = stillOpen ? remembered : (siblings[0]?.id ?? null);\n switchedTab = siblings.find((t) => t.id === activeTabId);\n return {\n activeWorkspaceCwd: cwd,\n activeTabId,\n lastActiveTabByWorkspace: activeTabId\n ? { ...s.lastActiveTabByWorkspace, [cwd]: activeTabId }\n : s.lastActiveTabByWorkspace,\n };\n });\n syncActiveSession(switchedTab);\n },\n\n reorder(fromId, toId) {\n set((s) => {\n const fromIdx = s.tabs.findIndex((t) => t.id === fromId);\n const toIdx = s.tabs.findIndex((t) => t.id === toId);\n if (fromIdx === -1 || toIdx === -1 || fromIdx === toIdx) return s;\n const tabs = s.tabs.slice();\n const [moved] = tabs.splice(fromIdx, 1);\n if (!moved) return s;\n tabs.splice(toIdx, 0, moved);\n return { tabs };\n });\n },\n\n updateLabel(id, label) {\n set((s) => ({ tabs: s.tabs.map((t) => (t.id === id ? { ...t, label } : t)) }));\n },\n\n updateData(id, data) {\n set((s) => ({\n tabs: s.tabs.map((t) => (t.id === id ? { ...t, data: { ...t.data, ...data } } : t)),\n }));\n },\n}));\n\n// ─── Tab id / open helpers (POC id conventions: \"chat-<id>\", \"file-<path>\", …) ────────────\n\nexport const tabIds = {\n chat: (sessionId: string) => `chat-${sessionId}`,\n file: (path: string) => `file-${path}`,\n diff: (path: string, staged: boolean) => `diff-${path}${staged ? \"-staged\" : \"\"}`,\n terminal: (slotOrToken: number | string) => `term-${slotOrToken}`,\n};\n\nlet terminalCount = 0;\n\n/** Open a brand-new terminal tab against a workspace cwd (no slot yet — `TerminalPanel` creates\n * one on mount). Shared by the `Ctrl/Cmd+T` shortcut (`use-shortcuts.ts`) and the TabStrip's \"+\"\n * button so both paths mint tab ids/labels identically. */\nexport function openNewTerminal(workspaceCwd: string): void {\n terminalCount += 1;\n useTabStore.getState().open({\n id: tabIds.terminal(`new-${terminalCount}`),\n kind: \"terminal\",\n label: `Terminal ${terminalCount}`,\n closable: true,\n data: { slot: null, cwd: workspaceCwd },\n workspaceCwd,\n });\n}\n\n/** Open a brand-new chat: creates a session against a workspace cwd and opens/focuses its chat\n * tab. Shared by the sidebar's \"+ New conversation\" button, `open-workspace.ts`'s create-new\n * path, and the TabStrip's \"+\" button — so every caller mints identical tab ids/labels (mirrors\n * `openNewTerminal` above). `workspaceCwd` MUST already be normalized by the caller (same\n * contract as `openNewTerminal`).\n *\n * Materializes eagerly: the tab and sidebar row appear synchronously from `createSession`/`open`\n * above, then `ensureMaterialized` (`materialize.ts`) commits a real, persisted `AgentRecord` in\n * the background — no spawn yet, just the record (`agent-service.ts` `handleCreate`'s\n * deferred-draft branch) — so every \"New chat\" has a bound `agentId` before the user does\n * anything. Best-effort: a failure here (or opening while disconnected, guarded by `if (client)`)\n * is retried by `Composer.submit`'s own `ensureMaterialized` call on the first send. */\nexport function openNewChat(workspaceCwd: string): void {\n const id = useSessionStore.getState().createSession(workspaceCwd);\n useTabStore.getState().open({\n id: tabIds.chat(id),\n kind: \"chat\",\n label: \"New chat\",\n closable: true,\n data: { sessionId: id },\n workspaceCwd,\n });\n\n const client = useConnectionStore.getState().client;\n if (client) {\n void ensureMaterialized(client, id).catch(() => {\n // Best-effort: `Composer.submit`'s own `ensureMaterialized` retries on the first send.\n });\n }\n}\n\n/** Close a tab, discarding a never-used chat's draft record with it (`materialize.ts`\n * `discardIfEmpty`). Every UI close path MUST go through this, not `useTabStore.close` directly.\n */\nexport function closeTab(tabId: string): void {\n const tab = useTabStore.getState().tabs.find((t) => t.id === tabId);\n useTabStore.getState().close(tabId);\n if (tab?.kind !== \"chat\") return;\n const { sessionId } = tab.data as ChatTabData;\n void discardIfEmpty(useConnectionStore.getState().client, sessionId);\n}\n","/**\n * Explorer store — the right-sidebar Files tab's tree-expansion state (POC `currentFilePath`\n * global, superseded by a tree view: POC_TO_APP_PLAN_UI.md §4.7 / §3 `stores/explorer-store.ts`).\n * File listing data itself is TanStack Query cache (see `hooks/use-explorer.ts`), NOT store\n * state — this store only tracks *which directories are expanded*, not their contents.\n *\n * `expandedByRoot` remembers each workspace's expanded-path set across workspace-tab switches,\n * keyed by that workspace's resolved root path — restored when `setRoot` is called again with the\n * same root (in-memory only, for the lifetime of the session; not persisted to disk).\n */\n\nimport { create } from \"zustand\";\nimport type { PiStudioClient } from \"@av-pi-studio/client\";\n\ninterface ExplorerStoreState {\n /** The active workspace's resolved cwd — the tree root. Always a member of `expanded`; there\n * is no row/chevron for it, its children render at depth 0 (§4.7: the file explorer is scoped\n * to the workspace, not a general filesystem browser — there is nothing above this to show). */\n rootPath: string;\n /** Absolute paths of directories currently expanded in the tree (always includes `rootPath`). */\n expanded: Set<string>;\n /** Session memory of each workspace's `expanded` set, keyed by its resolved root path. Keys\n * are inserted dynamically as workspaces are visited this session, so a `Map` (not `Record`)\n * is the right fit here. */\n expandedByRoot: Map<string, Set<string>>;\n\n /** Switch the tree root — called when the active workspace changes. Saves the outgoing root's\n * `expanded` set into `expandedByRoot` and restores the incoming root's remembered set (or\n * seeds a fresh one containing just the root, if this root has never been visited this\n * session). */\n setRoot(path: string): void;\n toggle(path: string): void;\n /** In-progress inline \"new file\"/\"new folder\" row in the tree: the directory it will be\n * created in, and which kind. Only one draft at a time. */\n draft: { parentPath: string; kind: \"file\" | \"directory\" } | null;\n /** Begin an inline draft under `parentPath`, expanding it so the draft row is visible. */\n startDraft(parentPath: string, kind: \"file\" | \"directory\"): void;\n cancelDraft(): void;\n}\n\nexport const useExplorerStore = create<ExplorerStoreState>()((set) => ({\n rootPath: \"\",\n expanded: new Set(),\n expandedByRoot: new Map(),\n draft: null,\n\n setRoot: (path) =>\n set((s) => {\n const expandedByRoot = new Map(s.expandedByRoot);\n if (s.rootPath) expandedByRoot.set(s.rootPath, s.expanded);\n const remembered = expandedByRoot.get(path);\n const expanded = new Set(remembered);\n expanded.add(path);\n return { rootPath: path, expanded, expandedByRoot, draft: null };\n }),\n\n toggle: (path) =>\n set((s) => {\n if (path === s.rootPath) return s;\n const expanded = new Set(s.expanded);\n if (expanded.has(path)) expanded.delete(path);\n else expanded.add(path);\n const expandedByRoot = new Map(s.expandedByRoot);\n expandedByRoot.set(s.rootPath, expanded);\n return { expanded, expandedByRoot };\n }),\n\n startDraft: (parentPath, kind) =>\n set((s) => {\n const expanded = new Set(s.expanded);\n expanded.add(parentPath);\n const expandedByRoot = new Map(s.expandedByRoot);\n if (s.rootPath) expandedByRoot.set(s.rootPath, expanded);\n return { draft: { parentPath, kind }, expanded, expandedByRoot };\n }),\n\n cancelDraft: () => set({ draft: null }),\n}));\ninterface ExplorerEntry {\n name?: string;\n kind?: string;\n type?: string;\n}\n\ninterface ExplorerRpcResult {\n entries?: ExplorerEntry[];\n}\n\ninterface ExplorerRpcResponse {\n result?: ExplorerRpcResult;\n entries?: ExplorerEntry[];\n}\n\nlet cachedHomeDir: string | null = null;\ntype HomeDirListener = (homeDir: string) => void;\nconst homeDirListeners = new Set<HomeDirListener>();\n\n/** Subscribe to the resolved home dir becoming available; returns an unsubscribe function. */\nexport function onHomeDirResolved(listener: HomeDirListener): () => void {\n homeDirListeners.add(listener);\n return () => homeDirListeners.delete(listener);\n}\n\n/**\n * Ports the POC's `resolveHome()` (chat.html ~line 1055-1056): probes `file_explorer_request`\n * for `/home`, takes the first directory entry, and caches the result for the process lifetime.\n * A failure (e.g. requested before the connection is fully open) returns `/tmp` for that call but\n * is NOT cached, so a later call — once connected — can still resolve the real home dir.\n */\nexport async function resolveHome(client: PiStudioClient): Promise<string> {\n if (cachedHomeDir) return cachedHomeDir;\n try {\n const response = await client.connection.request<ExplorerRpcResponse>(\"file_explorer_request\", {\n path: \"/home\",\n });\n const entries = response.result?.entries ?? response.entries ?? [];\n const first = entries.find((e) => e.kind === \"directory\" || e.type === \"directory\");\n cachedHomeDir = \"/home/\" + (first?.name || \"user\");\n } catch {\n return \"/tmp\";\n }\n for (const listener of homeDirListeners) listener(cachedHomeDir);\n return cachedHomeDir;\n}\n\n/** Synchronous best-effort peek at the cached home dir (populated once `resolveHome` has run). */\nexport function peekCachedHomeDir(): string | null {\n return cachedHomeDir;\n}\n\n/** Expand a leading `~` (POC's `(path === \"~\" || path.startsWith(\"~/\")) ? …` pattern). */\nexport async function resolveTildePath(client: PiStudioClient, path: string): Promise<string> {\n if (path === \"~\" || path.startsWith(\"~/\")) {\n const home = await resolveHome(client);\n return home + path.slice(1);\n }\n return path;\n}\n","/**\n * Resolves (and reactively exposes) the daemon's home directory, so `~`-relative session `cwd`s\n * can be normalized against their absolute form for workspace grouping (§4.3 sidebar tree).\n * Triggers `resolveHome` once the connection is fully open; re-renders the caller once it resolves.\n */\n\nimport { useEffect, useState } from \"react\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { resolveHome, peekCachedHomeDir, onHomeDirResolved } from \"@pi-studio-ui/stores/explorer-store.js\";\n\nexport function useHomeDir(): string | null {\n const client = useConnectionStore((s) => s.client);\n const status = useConnectionStore((s) => s.status);\n const [homeDir, setHomeDir] = useState<string | null>(peekCachedHomeDir());\n\n useEffect(() => onHomeDirResolved(setHomeDir), []);\n\n useEffect(() => {\n if (!client || status !== \"open\" || homeDir) return;\n void resolveHome(client);\n }, [client, status, homeDir]);\n\n return homeDir;\n}\n","// AgentStatusDot — state-bucket → color mapping.\n// ui-components.md § Status dots & avatars\n\n// Agent status strings that the daemon can send (from agent-sessions.md).\nexport type AgentStatus =\n | \"idle\"\n | \"running\"\n | \"waiting\"\n | \"finished\"\n | \"error\"\n | \"queued\"\n | \"archived\";\n\nexport type AttentionReason = \"finished\" | \"error\" | \"permission\";\n\nexport type StatusDotInput = {\n status: AgentStatus | undefined | null;\n requiresAttention?: boolean;\n attentionReason?: AttentionReason;\n pendingPermissionCount?: number;\n showInactive?: boolean;\n};\n\nexport type StatusDotColor =\n | \"accent\" // running/active\n | \"statusSuccess\" // finished-ok / success\n | \"statusDanger\" // error / attention\n | \"statusWarning\" // waiting / pending permission\n | \"foregroundMuted\"; // idle / inactive\n\n/**\n * Returns the theme token key for the status dot color, or null if the dot\n * should not be rendered (missing/invalid status and showInactive=false).\n */\nexport function statusDotColor(input: StatusDotInput): StatusDotColor | null {\n const { status, requiresAttention, attentionReason, showInactive } = input;\n\n if (!status) return null;\n\n // Attention overrides the base status color.\n if (requiresAttention) {\n switch (attentionReason) {\n case \"permission\":\n return \"statusWarning\";\n case \"error\":\n return \"statusDanger\";\n case \"finished\":\n default:\n return \"statusSuccess\";\n }\n }\n\n switch (status) {\n case \"running\":\n case \"queued\":\n return \"accent\";\n case \"waiting\":\n return \"statusWarning\";\n case \"finished\":\n return \"statusSuccess\";\n case \"error\":\n return \"statusDanger\";\n case \"idle\":\n case \"archived\":\n return showInactive ? \"foregroundMuted\" : null;\n }\n}\n\n// Status dot dimensions per the spec (8×8 round).\nexport const STATUS_DOT_SIZE = 8;\n","/**\n * AgentStatusDot — 8×8 circle whose color reflects agent status.\n * ui-components.md § Status dots & avatars\n */\n\nimport styles from \"./StatusDot.module.css\";\nimport {\n statusDotColor,\n STATUS_DOT_SIZE,\n type StatusDotInput,\n} from \"@pi-studio-ui/ui/status-dot.js\";\n\nconst COLOR_TOKEN_VAR: Record<string, string> = {\n accent: \"var(--pi-color-accent)\",\n statusSuccess: \"var(--pi-color-statusSuccess)\",\n statusDanger: \"var(--pi-color-statusDanger)\",\n statusWarning: \"var(--pi-color-statusWarning)\",\n foregroundMuted: \"var(--pi-color-foregroundMuted)\",\n};\n\nexport type StatusDotProps = StatusDotInput & {\n className?: string;\n};\n\nexport function StatusDot({ className, ...input }: StatusDotProps) {\n const colorToken = statusDotColor(input);\n if (!colorToken) return null;\n\n // Running (and not overridden by an attention state) gets a spinning ring instead of a flat\n // dot — a static \"blue circle\" reads as just another status color, not as work in progress.\n const spinning = input.status === \"running\" && !input.requiresAttention;\n const color = spinning\n ? \"var(--pi-color-accentBright, #a2b4d7)\" // brighter than plain accent — must read clearly\n : // against the dark surface at the small size this ring renders at.\n (COLOR_TOKEN_VAR[colorToken] ?? \"currentColor\");\n\n return (\n <span\n className={`${spinning ? styles.spinner : styles.dot}${className ? ` ${className}` : \"\"}`}\n style={\n spinning\n ? { borderTopColor: color, borderRightColor: color }\n : { width: STATUS_DOT_SIZE, height: STATUS_DOT_SIZE, backgroundColor: color }\n }\n role=\"presentation\"\n />\n );\n}\n","/**\n * Protocol `AgentStatus` (initializing/idle/running/error/closed) → design-system\n * `StatusDotInput.status` (idle/running/waiting/finished/error/queued/archived). The two enums\n * differ because the design system's dot vocabulary predates this protocol; this is the single\n * translation point.\n */\n\nimport type { AgentStatus as ProtocolAgentStatus } from \"@av-pi-studio/protocol\";\nimport type { AgentStatus as DotAgentStatus } from \"@pi-studio-ui/ui/status-dot.js\";\n\nconst MAP: Record<ProtocolAgentStatus, DotAgentStatus> = {\n initializing: \"queued\",\n idle: \"idle\",\n running: \"running\",\n error: \"error\",\n closed: \"finished\",\n};\n\nexport function toDotStatus(status: ProtocolAgentStatus | \"idle\"): DotAgentStatus {\n return MAP[status] ?? \"idle\";\n}\n","/**\n * One row in the session list (POC `renderSessions` per-item markup, POC_TO_APP_PLAN_UI.md §4.2).\n */\n\nimport { MoreVertical } from \"lucide-react\";\nimport { clsx } from \"clsx\";\nimport { StatusDot } from \"@pi-studio-ui/components/primitives/StatusDot.js\";\nimport type { SessionEntry } from \"@pi-studio-ui/stores/session-store.js\";\nimport { toDotStatus } from \"./status-map.js\";\nimport styles from \"./SessionList.module.css\";\n\nexport interface SessionItemProps {\n session: SessionEntry;\n active: boolean;\n onSelect: () => void;\n onOpenMenu: (x: number, y: number) => void;\n}\n\nexport function SessionItem({ session, active, onSelect, onOpenMenu }: SessionItemProps) {\n return (\n <div\n className={clsx(styles.item, active && styles.active)}\n onClick={onSelect}\n onContextMenu={(ev) => {\n ev.preventDefault();\n onOpenMenu(ev.clientX, ev.clientY);\n }}\n >\n <div className={styles.title}>\n <StatusDot status={toDotStatus(session.status)} showInactive />\n <span>{session.title}</span>\n </div>\n <div className={styles.meta}>\n {session.cwd || \"~\"} · {session.agentId ? session.agentId.slice(0, 8) : \"new\"} ·{\" \"}\n {session.userMessageCount} msgs\n </div>\n <button\n type=\"button\"\n className={styles.menuBtn}\n title=\"Actions\"\n onClick={(ev) => {\n ev.stopPropagation();\n const rect = ev.currentTarget.getBoundingClientRect();\n onOpenMenu(rect.left, rect.bottom);\n }}\n >\n <MoreVertical size={14} />\n </button>\n </div>\n );\n}\n","/**\n * Session context menu — Radix DropdownMenu anchored at cursor coordinates, replacing the POC's\n * manually-positioned `#session-menu` (POC_TO_APP_PLAN_UI.md §4.7). Actions: rename\n * (`update_agent` labels), stop (`interrupt_agent`), archive (`archive_agent` — soft delete, keeps\n * the record for resume), delete (`delete_agent` — hard delete, no trace). Both archive and delete\n * are real remote RPCs: local session/tab state is only removed after the daemon confirms, so a\n * failure (disconnected, agent already gone, etc.) surfaces instead of silently leaving the record\n * on disk while the UI pretends it's gone.\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport * as DropdownMenu from \"@radix-ui/react-dropdown-menu\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { useSessionStore } from \"@pi-studio-ui/stores/session-store.js\";\nimport { useTabStore, tabIds } from \"@pi-studio-ui/stores/tab-store.js\";\nimport { useUiStore } from \"@pi-studio-ui/stores/ui-store.js\";\nimport styles from \"./SessionContextMenu.module.css\";\n\nexport function SessionContextMenu() {\n const menu = useUiStore((s) => s.sessionMenu);\n const closeSessionMenu = useUiStore((s) => s.closeSessionMenu);\n const client = useConnectionStore((s) => s.client);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const [open, setOpen] = useState(false);\n\n useEffect(() => {\n setOpen(menu !== null);\n }, [menu]);\n\n const session = useSessionStore((s) => (menu ? s.sessions[menu.sessionId] : undefined));\n\n if (!menu) return null;\n const canStop = Boolean(session?.agentId) && session?.status === \"running\";\n\n function handleOpenChange(next: boolean) {\n setOpen(next);\n if (!next) closeSessionMenu();\n }\n\n function rename() {\n if (!session) return;\n const next = window.prompt(\"Rename:\", session.title);\n if (next?.trim()) {\n const title = next.trim();\n useSessionStore.getState().setTitle(session.id, title);\n useTabStore.getState().updateLabel(tabIds.chat(session.id), title);\n if (session.agentId) void client?.agent(session.agentId).update({ title }).catch(() => {});\n }\n closeSessionMenu();\n }\n\n function stop() {\n if (session?.agentId) void client?.agent(session.agentId).interrupt().catch(() => {});\n closeSessionMenu();\n }\n\n /** Remove the session/tab from local state only — called after a confirmed remote success, or\n * directly for a session with no `agentId` (never sent a message, nothing exists server-side). */\n function removeLocal(sessionId: string) {\n useSessionStore.getState().remove(sessionId);\n useTabStore.getState().close(tabIds.chat(sessionId));\n }\n\n async function archive() {\n if (!session) return;\n if (!window.confirm(\"Archive this agent? It can be resumed later.\")) return closeSessionMenu();\n closeSessionMenu();\n if (!session.agentId) {\n removeLocal(session.id);\n return;\n }\n if (!client) {\n window.alert(\"Not connected — cannot archive remotely.\");\n return;\n }\n try {\n await client.agent(session.agentId).archive();\n removeLocal(session.id);\n } catch (err) {\n window.alert(`Failed to archive: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n async function remove() {\n if (!session) return;\n if (!window.confirm(\"Permanently delete this agent? This cannot be undone.\")) {\n return closeSessionMenu();\n }\n closeSessionMenu();\n if (!session.agentId) {\n removeLocal(session.id);\n return;\n }\n if (!client) {\n window.alert(\"Not connected — cannot delete remotely.\");\n return;\n }\n try {\n await client.agent(session.agentId).delete();\n removeLocal(session.id);\n } catch (err) {\n window.alert(`Failed to delete: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n return (\n <DropdownMenu.Root open={open} onOpenChange={handleOpenChange} modal={false}>\n <DropdownMenu.Trigger asChild>\n <button\n ref={triggerRef}\n type=\"button\"\n className={styles.trigger}\n style={{ left: menu.x, top: menu.y }}\n aria-hidden\n tabIndex={-1}\n />\n </DropdownMenu.Trigger>\n <DropdownMenu.Portal>\n <DropdownMenu.Content className={styles.content} align=\"start\" sideOffset={2}>\n <DropdownMenu.Item className={styles.item} onSelect={rename}>\n Rename\n </DropdownMenu.Item>\n <DropdownMenu.Item className={styles.item} disabled={!canStop} onSelect={stop}>\n Stop agent\n </DropdownMenu.Item>\n <DropdownMenu.Separator className={styles.sep} />\n <DropdownMenu.Item className={`${styles.item} ${styles.danger}`} onSelect={archive}>\n Archive\n </DropdownMenu.Item>\n <DropdownMenu.Item className={`${styles.item} ${styles.danger}`} onSelect={remove}>\n Delete permanently\n </DropdownMenu.Item>\n </DropdownMenu.Content>\n </DropdownMenu.Portal>\n </DropdownMenu.Root>\n );\n}\n","/**\n * Collapsible workspace header in the sidebar tree — one per distinct session `cwd`\n * (POC_TO_APP_PLAN_UI.md §4.3 workspace grouping). Clicking anywhere on the header (or its\n * chevron) toggles collapse/expand; opening a session is a separate, explicit action (click a\n * session row, or the \"New conversation\" button).\n */\n\nimport { ChevronDown, ChevronRight, FolderClosed, Plus } from \"lucide-react\";\nimport { clsx } from \"clsx\";\nimport { Button } from \"@pi-studio-ui/components/primitives/Button.js\";\nimport styles from \"./SessionList.module.css\";\n\nexport interface WorkspaceGroupHeaderProps {\n label: string;\n cwd: string;\n sessionCount: number;\n collapsed: boolean;\n onToggleCollapsed: () => void;\n onNewSession: () => void;\n}\n\nexport function WorkspaceGroupHeader({\n label,\n cwd,\n sessionCount,\n collapsed,\n onToggleCollapsed,\n onNewSession,\n}: WorkspaceGroupHeaderProps) {\n return (\n <div className={styles.workspaceHeader} onClick={onToggleCollapsed} title={cwd}>\n <button\n type=\"button\"\n className={styles.workspaceChevron}\n aria-label={collapsed ? \"Expand workspace\" : \"Collapse workspace\"}\n onClick={(ev) => {\n ev.stopPropagation();\n onToggleCollapsed();\n }}\n >\n {collapsed ? <ChevronRight size={13} /> : <ChevronDown size={13} />}\n </button>\n <FolderClosed size={13} className={clsx(styles.workspaceIcon)} />\n <span className={styles.workspaceLabel}>{label}</span>\n <span className={styles.workspaceCount}>{sessionCount}</span>\n <Button\n size=\"xs\"\n variant=\"ghost\"\n iconOnly\n title=\"New conversation\"\n className={styles.newSessionBtn}\n onClick={(ev) => {\n ev.stopPropagation();\n onNewSession();\n }}\n >\n <Plus size={13} />\n </Button>\n </div>\n );\n}\n","/**\n * Groups sidebar sessions by workspace (cwd) — each workspace is a project folder that can host\n * several chat sessions, nested under a collapsible header (POC_TO_APP_PLAN_UI.md §4.3 follow-up:\n * workspace-tree sidebar).\n */\n\nimport type { SessionEntry } from \"@pi-studio-ui/stores/session-store.js\";\n\nexport interface WorkspaceGroup {\n /** Normalized (tilde-expanded when known) cwd — the group identity and RPC-ready path. */\n cwd: string;\n sessions: SessionEntry[];\n}\n\n/** Expand a leading `~` against `homeDir` when known; otherwise return `path` unchanged. Single\n * source of truth for workspace identity — every consumer that needs to compare/derive a\n * workspace cwd (grouping, tab scoping, the open-workspace resolver) MUST go through this, or a\n * second normalization drifting from this one reintroduces tilde/absolute duplicate workspaces. */\nexport function normalizeCwd(path: string, homeDir: string | null): string {\n if (homeDir && (path === \"~\" || path.startsWith(\"~/\"))) return homeDir + path.slice(1);\n return path;\n}\n\n/**\n * Buckets sessions by `cwd` (tilde-normalized against `homeDir` so `~/proj` and\n * `/home/user/proj` merge into one workspace once the home dir is known), preserving `order`'s\n * recency: a workspace's position is its first appearance in `order`, and sessions within it keep\n * their relative `order` position too — so the most recently created/active session in each\n * workspace sorts first.\n */\nexport function groupSessionsByWorkspace(\n order: string[],\n sessions: Record<string, SessionEntry>,\n homeDir: string | null,\n): WorkspaceGroup[] {\n const groups = new Map<string, SessionEntry[]>();\n for (const id of order) {\n const session = sessions[id];\n if (!session) continue;\n const cwd = normalizeCwd(session.cwd || \"~\", homeDir);\n const bucket = groups.get(cwd);\n if (bucket) bucket.push(session);\n else groups.set(cwd, [session]);\n }\n return Array.from(groups, ([cwd, sessionsInGroup]) => ({ cwd, sessions: sessionsInGroup }));\n}\n\n/** Short display label for a workspace path — the last path segment, or `~` for the home dir. */\nexport function workspaceLabel(cwd: string): string {\n if (!cwd || cwd === \"~\") return \"~\";\n return cwd.split(\"/\").filter(Boolean).pop() || cwd;\n}\n","/**\n * Left sidebar session list (POC `#session-list`/`renderSessions`, POC_TO_APP_PLAN_UI.md §4.3).\n * Sessions are grouped into a collapsible tree by workspace (project `cwd`): clicking a workspace\n * header toggles that workspace's collapsed state; clicking a nested session opens that session.\n */\n\nimport { FolderOpen } from \"lucide-react\";\nimport { useSessionStore } from \"@pi-studio-ui/stores/session-store.js\";\nimport { useTabStore, tabIds, openNewChat } from \"@pi-studio-ui/stores/tab-store.js\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { useUiStore } from \"@pi-studio-ui/stores/ui-store.js\";\nimport { Button } from \"@pi-studio-ui/components/primitives/Button.js\";\nimport { useHomeDir } from \"@pi-studio-ui/hooks/use-home-dir.js\";\nimport { SessionItem } from \"./SessionItem.js\";\nimport { SessionContextMenu } from \"./SessionContextMenu.js\";\nimport { WorkspaceGroupHeader } from \"./WorkspaceGroupHeader.js\";\nimport { groupSessionsByWorkspace, normalizeCwd, workspaceLabel } from \"./workspace-grouping.js\";\nimport styles from \"./SessionList.module.css\";\n\nexport function SessionList() {\n const order = useSessionStore((s) => s.order);\n const sessions = useSessionStore((s) => s.sessions);\n const activeSessionId = useSessionStore((s) => s.activeSessionId);\n const activate = useSessionStore((s) => s.activate);\n const openTab = useTabStore((s) => s.open);\n const status = useConnectionStore((s) => s.status);\n const openSessionMenu = useUiStore((s) => s.openSessionMenu);\n const setCwd = useUiStore((s) => s.setCwd);\n const openCwdPicker = useUiStore((s) => s.openCwdPicker);\n const collapsedWorkspaces = useUiStore((s) => s.collapsedWorkspaces);\n const toggleWorkspaceCollapsed = useUiStore((s) => s.toggleWorkspaceCollapsed);\n const homeDir = useHomeDir();\n\n function handleNewSession(targetWorkspaceCwd: string) {\n const targetCwd = normalizeCwd(targetWorkspaceCwd || \"~\", homeDir);\n openNewChat(targetCwd);\n }\n\n function handleSelect(sessionId: string) {\n activate(sessionId);\n const session = sessions[sessionId];\n const targetCwd = normalizeCwd(session?.cwd || \"~\", homeDir);\n if (session?.cwd) setCwd(session.cwd);\n openTab({\n id: tabIds.chat(sessionId),\n kind: \"chat\",\n label: session?.title ?? \"Chat\",\n closable: true,\n data: { sessionId },\n workspaceCwd: targetCwd,\n });\n }\n\n const groups = groupSessionsByWorkspace(order, sessions, homeDir);\n\n return (\n <div className={styles.wrap}>\n <div className={styles.header}>\n <h3>Workspaces</h3>\n <Button\n size=\"xs\"\n variant=\"ghost\"\n iconOnly\n title={status !== \"open\" ? \"Connect to open a workspace\" : \"Open a workspace folder\"}\n disabled={status !== \"open\"}\n onClick={() => openCwdPicker()}\n >\n <FolderOpen size={14} />\n </Button>\n </div>\n <div className={styles.list}>\n {groups.length === 0 && (\n <div className={styles.emptyState}>\n {status !== \"open\" ? \"Not connected\" : \"No workspaces — open a folder to start\"}\n </div>\n )}\n {groups.map((group) => {\n const collapsed = collapsedWorkspaces.has(group.cwd);\n return (\n <div key={group.cwd} className={styles.workspaceGroup}>\n <WorkspaceGroupHeader\n label={workspaceLabel(group.cwd)}\n cwd={group.cwd}\n sessionCount={group.sessions.length}\n collapsed={collapsed}\n onToggleCollapsed={() => toggleWorkspaceCollapsed(group.cwd)}\n onNewSession={() => handleNewSession(group.cwd)}\n />\n {!collapsed && (\n <div className={styles.workspaceSessions}>\n {group.sessions.map((session) => (\n <SessionItem\n key={session.id}\n session={session}\n active={session.id === activeSessionId}\n onSelect={() => handleSelect(session.id)}\n onOpenMenu={(x, y) => openSessionMenu(session.id, x, y)}\n />\n ))}\n </div>\n )}\n </div>\n );\n })}\n </div>\n <SessionContextMenu />\n </div>\n );\n}\n","/**\n * TabStrip — dnd-kit sortable tab strip (POC `renderTabs`, POC_TO_APP_PLAN_UI.md §4.2).\n * Adds drag reorder (the POC couldn't reorder) + Radix Tooltip on truncated labels.\n */\n\nimport { MessageSquare, FileText, GitCompare, TerminalSquare, X, Plus } from \"lucide-react\";\nimport { clsx } from \"clsx\";\nimport * as DropdownMenu from \"@radix-ui/react-dropdown-menu\";\nimport {\n DndContext,\n closestCenter,\n PointerSensor,\n useSensor,\n useSensors,\n type DragEndEvent,\n} from \"@dnd-kit/core\";\nimport {\n SortableContext,\n horizontalListSortingStrategy,\n useSortable,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport {\n useTabStore,\n openNewChat,\n openNewTerminal,\n closeTab,\n type Tab,\n type TabKind,\n} from \"@pi-studio-ui/stores/tab-store.js\";\nimport styles from \"./TabStrip.module.css\";\n\nconst ICON_BY_KIND: Record<TabKind, typeof MessageSquare> = {\n chat: MessageSquare,\n file: FileText,\n diff: GitCompare,\n terminal: TerminalSquare,\n};\n\nfunction TabItem({ tab }: { tab: Tab }) {\n const activeTabId = useTabStore((s) => s.activeTabId);\n const activate = useTabStore((s) => s.activate);\n const Icon = ICON_BY_KIND[tab.kind];\n\n const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({\n id: tab.id,\n });\n\n return (\n <div\n ref={setNodeRef}\n className={clsx(styles.tab, tab.id === activeTabId && styles.active)}\n style={{\n transform: CSS.Transform.toString(transform),\n transition,\n opacity: isDragging ? 0.5 : 1,\n }}\n onClick={() => activate(tab.id)}\n onAuxClick={(ev) => {\n if (ev.button === 1 && tab.closable) closeTab(tab.id);\n }}\n title={tab.label}\n {...attributes}\n {...listeners}\n >\n <span className={styles.icon}>\n <Icon size={13} />\n </span>\n <span className={styles.label}>{tab.label}</span>\n {tab.closable && (\n <span\n className={styles.close}\n onClick={(ev) => {\n ev.stopPropagation();\n closeTab(tab.id);\n }}\n >\n <X size={12} />\n </span>\n )}\n </div>\n );\n}\n\n/** Trailing \"+\" control — opens a new chat or terminal in the currently visible workspace.\n * Rendered as a sibling of `SortableContext`, not inside it, so it's never draggable/sortable\n * and reorder/`closestCenter` collision detection never sees it (GitHub issue #8). Orphaned\n * terminals (a PTY running on the daemon with no open tab) don't need a menu entry here — they're\n * auto-reopened as tabs by `use-terminal-restore.ts` on connect, the same way chat sessions are. */\nfunction NewTabMenu({ workspaceCwd }: { workspaceCwd: string | null }) {\n const cwd = workspaceCwd ?? \"~\";\n return (\n <DropdownMenu.Root>\n <DropdownMenu.Trigger asChild>\n <button\n type=\"button\"\n className={styles.newTab}\n title=\"New tab\"\n disabled={!workspaceCwd}\n >\n <Plus size={14} />\n </button>\n </DropdownMenu.Trigger>\n <DropdownMenu.Portal>\n <DropdownMenu.Content className={styles.content} align=\"start\" sideOffset={4}>\n <DropdownMenu.Item className={styles.item} onSelect={() => openNewChat(cwd)}>\n <MessageSquare size={13} className={styles.itemIcon} />\n New chat\n </DropdownMenu.Item>\n <DropdownMenu.Item className={styles.item} onSelect={() => openNewTerminal(cwd)}>\n <TerminalSquare size={13} className={styles.itemIcon} />\n New terminal\n </DropdownMenu.Item>\n </DropdownMenu.Content>\n </DropdownMenu.Portal>\n </DropdownMenu.Root>\n );\n}\n\nexport function TabStrip() {\n const tabs = useTabStore((s) => s.tabs);\n const activeWorkspaceCwd = useTabStore((s) => s.activeWorkspaceCwd);\n const reorder = useTabStore((s) => s.reorder);\n const visibleTabs = tabs.filter((t) => t.workspaceCwd === activeWorkspaceCwd);\n const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 4 } }));\n\n function handleDragEnd(event: DragEndEvent) {\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n reorder(String(active.id), String(over.id));\n }\n\n return (\n <div className={styles.strip}>\n <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>\n <SortableContext\n items={visibleTabs.map((t) => t.id)}\n strategy={horizontalListSortingStrategy}\n >\n {visibleTabs.map((tab) => (\n <TabItem key={tab.id} tab={tab} />\n ))}\n </SortableContext>\n </DndContext>\n <NewTabMenu workspaceCwd={activeWorkspaceCwd} />\n </div>\n );\n}\n","/**\n * Spinner — a thin activity-indicator circle.\n * ui-components.md § Feedback\n */\n\nimport styles from \"./Spinner.module.css\";\n\nexport type SpinnerSize = \"xs\" | \"sm\" | \"md\" | \"lg\";\nconst SPINNER_SIZE_PX: Record<SpinnerSize, number> = { xs: 12, sm: 16, md: 20, lg: 24 };\nconst SPINNER_BORDER_PX: Record<SpinnerSize, number> = { xs: 1, sm: 2, md: 2, lg: 3 };\n\nexport interface SpinnerProps {\n size?: SpinnerSize | number;\n /** CSS color string; defaults to currentColor. */\n color?: string;\n className?: string;\n \"aria-label\"?: string;\n}\n\nexport function Spinner({\n size = \"md\",\n color = \"currentColor\",\n className,\n \"aria-label\": ariaLabel = \"Loading\",\n}: SpinnerProps) {\n const px = typeof size === \"number\" ? size : SPINNER_SIZE_PX[size];\n const borderPx =\n typeof size === \"number\"\n ? 2\n : SPINNER_BORDER_PX[size as SpinnerSize];\n\n return (\n <span\n className={`${styles.spinner}${className ? ` ${className}` : \"\"}`}\n role=\"status\"\n aria-label={ariaLabel}\n style={{\n width: px,\n height: px,\n borderWidth: borderPx,\n color,\n }}\n />\n );\n}\n","/**\n * Tab kind → panel component map (POC `createTabPanel` switch, POC_TO_APP_PLAN_UI.md §4.2).\n * Panels are lazy-loaded — the terminal (xterm) and diff/highlighter modules only pay their\n * bundle cost once a tab of that kind actually opens (§6 Performance strategy).\n */\n\nimport { lazy, type ComponentType } from \"react\";\nimport type { Tab } from \"@pi-studio-ui/stores/tab-store.js\";\n\nexport interface PanelProps {\n tab: Tab;\n}\n\nconst ChatPanel = lazy(() => import(\"../chat/ChatPanel.js\").then((m) => ({ default: m.ChatPanel })));\nconst FilePanel = lazy(() => import(\"../files/FilePanel.js\").then((m) => ({ default: m.FilePanel })));\nconst TerminalPanel = lazy(() =>\n import(\"../terminal/TerminalPanel.js\").then((m) => ({ default: m.TerminalPanel })),\n);\n\nexport const PANEL_BY_KIND: Record<Tab[\"kind\"], ComponentType<PanelProps>> = {\n chat: ChatPanel,\n file: FilePanel,\n diff: FilePanel, // FilePanel handles both file/diff view toggle internally (POC §4.5)\n terminal: TerminalPanel,\n};\n","/**\n * TabPanelHost — mounts one panel per open tab via `panel-registry`, keeps inactive panels\n * mounted but hidden (`display:none`), preserving scroll/terminal state across tab switches\n * exactly like the POC's DOM-persisted panels (POC_TO_APP_PLAN_UI.md §4.2), but through React\n * reconciliation instead of `data-tab-panel` querying. This now spans workspace switches too\n * (§4.7 follow-up: workspace-scoped tabs) — ALL tabs across every workspace stay mounted; only\n * the strip/empty-state visibility is workspace-scoped, so switching workspaces never tears down\n * a live terminal or loses scroll position in another workspace's tabs.\n *\n * Two distinct empty states: no workspace open at all (`activeWorkspaceCwd === null`, with an\n * \"Open Workspace\" CTA) vs. a workspace in view with no tabs opened in it yet.\n */\n\nimport { Suspense } from \"react\";\nimport { clsx } from \"clsx\";\nimport { FolderOpen } from \"lucide-react\";\nimport { Spinner } from \"@pi-studio-ui/components/primitives/Spinner.js\";\nimport { Button } from \"@pi-studio-ui/components/primitives/Button.js\";\nimport { useTabStore } from \"@pi-studio-ui/stores/tab-store.js\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { useUiStore } from \"@pi-studio-ui/stores/ui-store.js\";\nimport { PANEL_BY_KIND } from \"./panel-registry.js\";\nimport styles from \"./TabPanelHost.module.css\";\n\nexport function TabPanelHost() {\n const tabs = useTabStore((s) => s.tabs);\n const activeTabId = useTabStore((s) => s.activeTabId);\n const activeWorkspaceCwd = useTabStore((s) => s.activeWorkspaceCwd);\n const status = useConnectionStore((s) => s.status);\n const openCwdPicker = useUiStore((s) => s.openCwdPicker);\n const hasTabsInWorkspace = tabs.some((t) => t.workspaceCwd === activeWorkspaceCwd);\n\n if (activeWorkspaceCwd === null) {\n return (\n <div className={styles.area}>\n <div className={styles.emptyStack}>\n <div className={styles.emptyTitle}>No workspace open</div>\n <div className={styles.emptyHint}>Open a project folder to start a chat.</div>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n leftIcon={<FolderOpen size={14} />}\n disabled={status !== \"open\"}\n title={status !== \"open\" ? \"Connect to open a workspace\" : \"Open a workspace folder\"}\n onClick={() => openCwdPicker()}\n >\n Open Workspace\n </Button>\n </div>\n </div>\n );\n }\n\n if (!hasTabsInWorkspace) {\n return (\n <div className={styles.area}>\n <div className={styles.empty}>No open tabs in this workspace</div>\n </div>\n );\n }\n\n return (\n <div className={styles.area}>\n {tabs.map((tab) => {\n const Panel = PANEL_BY_KIND[tab.kind];\n return (\n <div\n key={tab.id}\n className={clsx(styles.panel, tab.id === activeTabId && styles.active)}\n >\n <Suspense fallback={<Spinner size=\"md\" />}>\n <Panel tab={tab} />\n </Suspense>\n </div>\n );\n })}\n </div>\n );\n}\n","/**\n * TanStack Query key factory — one namespaced key builder per RPC domain so cache\n * invalidation (e.g. the `filesChanged` signal, POC_TO_APP_PLAN_UI.md §4.5) targets exactly\n * the affected queries.\n */\n\nexport const rpcKeys = {\n fileRead: (path: string) => [\"file\", \"read\", path] as const,\n fileDownload: (path: string) => [\"file\", \"download\", path] as const,\n fileDiff: (path: string, cwd: string, staged: boolean) =>\n [\"file\", \"diff\", path, cwd, staged] as const,\n explorer: (path: string) => [\"explorer\", path] as const,\n agentList: () => [\"agents\", \"list\"] as const,\n providerModels: (provider: string) => [\"providers\", \"models\", provider] as const,\n agentTimeline: (agentId: string) => [\"agents\", \"timeline\", agentId] as const,\n};\n","/**\n * `use-explorer` — TanStack Query wrapper over `file_explorer_request` (POC `loadFiles`,\n * POC_TO_APP_PLAN_UI.md §4.7 / §3 `hooks/use-explorer.ts`). Directory listings are read-through\n * cached, keyed by resolved path — components resolve `~` via `resolveTildePath` before calling.\n */\n\nimport { useQuery } from \"@tanstack/react-query\";\nimport type { PiStudioClient } from \"@av-pi-studio/client\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { rpcKeys } from \"@pi-studio-ui/lib/connection/rpc-keys.js\";\n\nexport interface ExplorerEntry {\n name: string;\n kind: \"file\" | \"directory\" | \"symlink\" | \"other\";\n size?: number;\n mtimeMs?: number;\n}\n\ninterface RawExplorerEntry {\n name?: string;\n kind?: string;\n type?: string;\n size?: number;\n mtimeMs?: number;\n}\n\ninterface ExplorerDirectoryResult {\n ok: true;\n kind: \"directory\";\n path: string;\n resolvedPath: string;\n entries: RawExplorerEntry[];\n}\n\ninterface ExplorerTextResult {\n ok: true;\n kind: \"text\";\n path: string;\n resolvedPath: string;\n content: string;\n size: number;\n}\n\ninterface ExplorerErrorResult {\n ok: false;\n error: string;\n}\n\ntype ExplorerResult = ExplorerDirectoryResult | ExplorerTextResult | ExplorerErrorResult;\n\ninterface ExplorerRpcResponse {\n result?: ExplorerResult;\n}\n\nexport interface ExplorerListing {\n path: string;\n entries: ExplorerEntry[];\n}\n\nfunction normalizeEntry(e: RawExplorerEntry): ExplorerEntry {\n const kind = e.kind === \"directory\" || e.type === \"directory\" ? \"directory\" : \"file\";\n return { name: e.name ?? \"\", kind, size: e.size, mtimeMs: e.mtimeMs };\n}\n\n/** Dirs first (alphabetical), then files (alphabetical) — POC `fileList.sort`. */\nfunction sortEntries(entries: ExplorerEntry[]): ExplorerEntry[] {\n return entries.slice().sort((a, b) => {\n if (a.kind !== b.kind) return a.kind === \"directory\" ? -1 : 1;\n return a.name.localeCompare(b.name);\n });\n}\n\n/** Shared fetch — used by both the single-path `useExplorer` and the multi-path tree query\n * (`useExplorerTree`, one TanStack Query per expanded directory). */\nexport async function fetchExplorerListing(\n client: PiStudioClient,\n path: string,\n): Promise<ExplorerListing> {\n const response = await client.connection.request<ExplorerRpcResponse>(\"file_explorer_request\", {\n path,\n });\n const result = response.result;\n if (!result || result.ok === false) {\n throw new Error(result && \"error\" in result ? result.error : \"explorer request failed\");\n }\n const entries = \"entries\" in result ? (result.entries ?? []) : [];\n return {\n path: result.resolvedPath ?? path,\n entries: sortEntries(entries.map(normalizeEntry)),\n };\n}\n\nexport function useExplorer(path: string, enabled = true) {\n const client = useConnectionStore((s) => s.client);\n\n return useQuery({\n queryKey: rpcKeys.explorer(path),\n queryFn: () => {\n if (!client) throw new Error(\"not connected\");\n return fetchExplorerListing(client, path);\n },\n enabled: Boolean(client) && Boolean(path) && enabled,\n });\n}\n","/**\n * `use-explorer-tree` — one TanStack Query per expanded directory path (POC_TO_APP_PLAN_UI.md\n * §4.7 tree-view follow-up). `FileExplorer`'s `explorer-store` tracks *which* paths are expanded;\n * this hook fetches each of them (via the same `file_explorer_request` + `rpcKeys.explorer` cache\n * `use-explorer.ts` uses for the single-path case, so results are shared/deduped) and hands back a\n * lookup keyed by path for `flattenTree` (`features/files/file-tree.ts`) to walk into rows.\n *\n * Collapsing a directory drops it from `expanded`, so its query stops being requested here — but\n * TanStack Query keeps the cached entry around under `rpcKeys.explorer(path)`, so re-expanding is\n * instant until the cache is invalidated (`filesChanged`, an upload, a delete, ...).\n */\n\nimport { useMemo } from \"react\";\nimport { useQueries } from \"@tanstack/react-query\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { rpcKeys } from \"@pi-studio-ui/lib/connection/rpc-keys.js\";\nimport { fetchExplorerListing, type ExplorerListing } from \"./use-explorer.js\";\n\nexport interface ExplorerTreeEntry {\n listing: ExplorerListing | undefined;\n isLoading: boolean;\n isError: boolean;\n error: unknown;\n}\n\nexport function useExplorerTree(expanded: Set<string>): Map<string, ExplorerTreeEntry> {\n const client = useConnectionStore((s) => s.client);\n const paths = useMemo(() => Array.from(expanded), [expanded]);\n\n const results = useQueries({\n queries: paths.map((path) => ({\n queryKey: rpcKeys.explorer(path),\n queryFn: (): Promise<ExplorerListing> => {\n if (!client) throw new Error(\"not connected\");\n return fetchExplorerListing(client, path);\n },\n enabled: Boolean(client) && Boolean(path),\n })),\n });\n\n return useMemo(() => {\n const map = new Map<string, ExplorerTreeEntry>();\n paths.forEach((path, i) => {\n const result = results[i];\n map.set(path, {\n listing: result?.data,\n isLoading: Boolean(result?.isLoading),\n isError: Boolean(result?.isError),\n error: result?.error,\n });\n });\n return map;\n }, [paths, results]);\n}\n","/**\n * Shared `FileTransferClient` accessor — one instance per daemon connection so every\n * transfer consumer (inline viewer downloads, save-to-disk downloads, uploads) shares the\n * single inbound-frame demuxer over the one WebSocket. Mirrors `TerminalPanel.tsx`'s\n * `routerFor` pattern.\n */\n\nimport type { DaemonClient } from \"@av-pi-studio/client\";\nimport { FileTransferClient } from \"@av-pi-studio/client\";\n\nconst transferByDaemon = new WeakMap<DaemonClient, FileTransferClient>();\n\nexport function transferFor(daemon: DaemonClient): FileTransferClient {\n let transfer = transferByDaemon.get(daemon);\n if (!transfer) {\n transfer = new FileTransferClient(daemon);\n transfer.start();\n transferByDaemon.set(daemon, transfer);\n }\n return transfer;\n}\n","/**\n * `use-file-transfer` — imperative upload + save-to-disk actions for the Files sidebar\n * (features/file-explorer-transfer.md § Binary transfer frames). Unlike `use-file-download`\n * (which produces object URLs for inline viewers), these are one-shot user-triggered actions:\n * push local bytes to the daemon, or pull daemon bytes into a browser file-save.\n */\n\nimport { useCallback } from \"react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { transferFor } from \"./file-transfer-instance.js\";\n\n/** Trigger a browser \"save file\" for the bytes assembled from a daemon-side path. */\nfunction saveBlob(bytes: Uint8Array, fileName: string, mimeType?: string): void {\n const blob = new Blob([Uint8Array.from(bytes)], {\n type: mimeType || \"application/octet-stream\",\n });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement(\"a\");\n anchor.href = url;\n anchor.download = fileName;\n anchor.rel = \"noopener\";\n document.body.appendChild(anchor);\n anchor.click();\n anchor.remove();\n // Revoke on the next tick — some browsers need the URL alive until the click is processed.\n setTimeout(() => URL.revokeObjectURL(url), 0);\n}\n\nexport interface FileTransferActions {\n /** Upload one browser `File` to `<dir>/<file.name>` on the daemon. */\n upload(dir: string, file: File): Promise<void>;\n /** Download `path` from the daemon and save it via the browser. */\n saveToDisk(path: string): Promise<void>;\n}\n\nexport function useFileTransfer(): FileTransferActions {\n const daemon = useConnectionStore((s) => s.daemon);\n const queryClient = useQueryClient();\n\n const upload = useCallback(\n async (dir: string, file: File) => {\n if (!daemon) throw new Error(\"not connected\");\n const target = dir ? `${dir}/${file.name}` : file.name;\n const bytes = new Uint8Array(await file.arrayBuffer());\n await transferFor(daemon).upload(target, bytes);\n // The daemon does not push an explorer update after an upload — invalidate the listing so\n // the new file appears (mirrors `files-changed` invalidation for tool writes).\n await queryClient.invalidateQueries({ queryKey: [\"explorer\"] });\n },\n [daemon, queryClient],\n );\n\n const saveToDisk = useCallback(\n async (path: string) => {\n if (!daemon) throw new Error(\"not connected\");\n const file = await transferFor(daemon).download(path);\n saveBlob(file.bytes, file.fileName || path.split(\"/\").pop() || \"download\", file.mimeType);\n },\n [daemon],\n );\n\n return { upload, saveToDisk };\n}\n","/**\n * Pure tree-flattening for the Files tab (POC_TO_APP_PLAN_UI.md §4.7 tree-view follow-up).\n * `FileExplorer.tsx` owns `expanded` (`explorer-store.ts`) and per-path listings\n * (`useExplorerTree`); this module just walks the two into the ordered row list the virtualizer\n * renders — no React, no store access, so it's cheap to unit-test in isolation.\n *\n * The workspace root itself is never a row (§4.7: this is a workspace-scoped browser, there is\n * nothing above the root to show and nothing to collapse it to) — its children start at depth 0.\n */\n\nimport type { ExplorerEntry } from \"@pi-studio-ui/hooks/use-explorer.js\";\nimport type { ExplorerTreeEntry } from \"@pi-studio-ui/hooks/use-explorer-tree.js\";\n\ninterface DirRow {\n kind: \"directory\";\n /** Absolute path — also the row's stable key and the `explorer-store` toggle/query key. */\n path: string;\n name: string;\n depth: number;\n expanded: boolean;\n}\n\ninterface FileRow {\n kind: \"file\";\n path: string;\n name: string;\n depth: number;\n}\n\n/** A directory's listing is loading, rendered indented under it — not itself\n * expandable/clickable. `path` is the *directory's* path (for a stable React key), not a real\n * file/dir on disk. */\ninterface LoadingRow {\n kind: \"loading\";\n path: string;\n depth: number;\n}\n\n/** A directory's listing failed to load. Same non-interactive placement as `LoadingRow`. */\ninterface ErrorRow {\n kind: \"error\";\n path: string;\n depth: number;\n message: string;\n}\n\n/** The inline \"new file\"/\"new folder\" name-entry row. `path` is a synthetic key (never a real\n * filesystem path) so the virtualizer's `getItemKey` stays unique. */\ninterface DraftRow {\n kind: \"draft\";\n path: string; // `${parentPath}::draft`\n depth: number;\n draftKind: \"file\" | \"directory\";\n parentPath: string;\n}\n\nexport type TreeRow = DirRow | FileRow | LoadingRow | ErrorRow | DraftRow;\n\n/** Which directory has an in-progress inline create row, and what kind it is. */\nexport interface TreeDraft {\n parentPath: string;\n kind: \"file\" | \"directory\";\n}\n\nfunction joinPath(dir: string, name: string): string {\n return dir.endsWith(\"/\") ? `${dir}${name}` : `${dir}/${name}`;\n}\n\nfunction pushChildren(\n rows: TreeRow[],\n dirPath: string,\n depth: number,\n expanded: Set<string>,\n tree: Map<string, ExplorerTreeEntry>,\n draft?: TreeDraft | null,\n): void {\n if (draft?.parentPath === dirPath) {\n rows.push({\n kind: \"draft\",\n path: `${dirPath}::draft`,\n depth,\n draftKind: draft.kind,\n parentPath: dirPath,\n });\n }\n const node = tree.get(dirPath);\n if (!node || node.isLoading) {\n rows.push({ kind: \"loading\", path: dirPath, depth });\n return;\n }\n if (node.isError) {\n const message = node.error instanceof Error ? node.error.message : \"failed to load\";\n rows.push({ kind: \"error\", path: dirPath, depth, message });\n return;\n }\n for (const entry of node.listing?.entries ?? []) {\n pushEntry(rows, dirPath, entry, depth, expanded, tree, draft);\n }\n}\nfunction pushEntry(\n rows: TreeRow[],\n parentPath: string,\n entry: ExplorerEntry,\n depth: number,\n expanded: Set<string>,\n tree: Map<string, ExplorerTreeEntry>,\n draft?: TreeDraft | null,\n): void {\n const path = joinPath(parentPath, entry.name);\n if (entry.kind !== \"directory\") {\n rows.push({ kind: \"file\", path, name: entry.name, depth });\n return;\n }\n const isExpanded = expanded.has(path);\n rows.push({ kind: \"directory\", path, name: entry.name, depth, expanded: isExpanded });\n if (isExpanded) pushChildren(rows, path, depth + 1, expanded, tree, draft);\n}\n\n/** Flatten the tree rooted at `rootPath` into the ordered rows currently visible. */\nexport function flattenTree(\n rootPath: string,\n expanded: Set<string>,\n tree: Map<string, ExplorerTreeEntry>,\n draft?: TreeDraft | null,\n): TreeRow[] {\n if (!rootPath) return [];\n const rows: TreeRow[] = [];\n pushChildren(rows, rootPath, 0, expanded, tree, draft);\n return rows;\n}\n","/**\n * TreeDraftRow — the inline \"new file\"/\"new folder\" name-entry row rendered in place of a real\n * tree row (`file-tree.ts`'s `DraftRow`). Owns the input's local text state so `TreeNode` stays\n * hook-free and presentational; submits the trimmed name up to `FileExplorer` via `onSubmit`,\n * or discards the draft via `onCancel` on Escape/blur/empty-submit.\n */\n\nimport { useState, type CSSProperties } from \"react\";\nimport { Folder, File as FileIcon } from \"lucide-react\";\nimport styles from \"./FileExplorer.module.css\";\n\nexport interface TreeDraftRowProps {\n draftKind: \"file\" | \"directory\";\n indentStyle: CSSProperties;\n onSubmit(name: string): void;\n onCancel(): void;\n}\n\nexport function TreeDraftRow({ draftKind, indentStyle, onSubmit, onCancel }: TreeDraftRowProps) {\n const [name, setName] = useState(\"\");\n\n return (\n <div className={styles.item} style={indentStyle} onClick={(e) => e.stopPropagation()}>\n <span className={styles.chevronSlot} />\n <span className={styles.icon}>\n {draftKind === \"directory\" ? <Folder size={14} /> : <FileIcon size={14} />}\n </span>\n <input\n className={styles.draftInput}\n autoFocus\n spellCheck={false}\n placeholder={draftKind === \"directory\" ? \"New folder\" : \"New file\"}\n value={name}\n onChange={(e) => setName(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n const trimmed = name.trim();\n if (trimmed) onSubmit(trimmed);\n else onCancel();\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n onCancel();\n }\n }}\n onBlur={onCancel}\n />\n </div>\n );\n}\n","/**\n * TreeNode — one row of the Files tab tree view (POC_TO_APP_PLAN_UI.md §4.7 tree-view follow-up).\n * Presentational only: `FileExplorer.tsx` computes the flattened `TreeRow[]` (`file-tree.ts`) and\n * hands each one here as a fixed-height virtualized row. A directory row toggles expansion; a\n * file row opens a tab; both show the \"⋮\" action button feeding the shared `FileContextMenu`.\n */\n\nimport { ChevronRight, Folder, File as FileIcon, MoreVertical } from \"lucide-react\";\nimport { clsx } from \"clsx\";\nimport type { TreeRow } from \"./file-tree.js\";\nimport { TreeDraftRow } from \"./TreeDraftRow.js\";\nimport styles from \"./FileExplorer.module.css\";\n\nexport interface TreeNodeProps {\n row: TreeRow;\n onToggle(path: string): void;\n onOpenFile(path: string): void;\n onContextMenu(path: string, isDirectory: boolean, x: number, y: number): void;\n onSubmitDraft(parentPath: string, name: string): void;\n onCancelDraft(): void;\n}\n\nconst INDENT_PX = 14;\n\nexport function TreeNode({\n row,\n onToggle,\n onOpenFile,\n onContextMenu,\n onSubmitDraft,\n onCancelDraft,\n}: TreeNodeProps) {\n const indent = { paddingLeft: 8 + row.depth * INDENT_PX };\n\n if (row.kind === \"loading\") {\n return (\n <div className={styles.statusRow} style={indent}>\n Loading…\n </div>\n );\n }\n if (row.kind === \"error\") {\n return (\n <div className={clsx(styles.statusRow, styles.statusRowError)} style={indent}>\n {row.message ?? \"failed to load\"}\n </div>\n );\n }\n if (row.kind === \"draft\") {\n return (\n <TreeDraftRow\n draftKind={row.draftKind}\n indentStyle={indent}\n onSubmit={(name) => onSubmitDraft(row.parentPath, name)}\n onCancel={onCancelDraft}\n />\n );\n }\n\n const isDirectory = row.kind === \"directory\";\n return (\n <div\n className={styles.item}\n style={indent}\n onClick={() => (isDirectory ? onToggle(row.path) : onOpenFile(row.path))}\n onContextMenu={(e) => {\n e.preventDefault();\n onContextMenu(row.path, isDirectory, e.clientX, e.clientY);\n }}\n >\n <span className={styles.chevronSlot}>\n {isDirectory && (\n <ChevronRight\n size={12}\n className={clsx(styles.chevron, row.expanded && styles.chevronOpen)}\n />\n )}\n </span>\n <span className={styles.icon}>\n {isDirectory ? <Folder size={14} /> : <FileIcon size={14} />}\n </span>\n <span className={isDirectory ? styles.dirName : styles.name}>{row.name}</span>\n <button\n type=\"button\"\n className={styles.rowAction}\n title=\"Actions\"\n onClick={(e) => {\n e.stopPropagation();\n const rect = e.currentTarget.getBoundingClientRect();\n onContextMenu(row.path, isDirectory, rect.left, rect.bottom);\n }}\n >\n <MoreVertical size={12} />\n </button>\n </div>\n );\n}\n","/**\n * File row context menu — Radix DropdownMenu anchored at cursor/button coordinates, mirroring\n * `SessionContextMenu.tsx`'s pattern. Opened via a per-row \"⋮\" button or right-click. Actions:\n * download (save-to-disk, files only) and delete (files + directories, recursive, confirmed).\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport * as DropdownMenu from \"@radix-ui/react-dropdown-menu\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useUiStore } from \"@pi-studio-ui/stores/ui-store.js\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { useExplorerStore } from \"@pi-studio-ui/stores/explorer-store.js\";\nimport { useFileTransfer } from \"@pi-studio-ui/hooks/use-file-transfer.js\";\nimport styles from \"./FileContextMenu.module.css\";\n\nexport function FileContextMenu() {\n const menu = useUiStore((s) => s.fileMenu);\n const closeFileMenu = useUiStore((s) => s.closeFileMenu);\n const client = useConnectionStore((s) => s.client);\n const startDraft = useExplorerStore((s) => s.startDraft);\n const { saveToDisk } = useFileTransfer();\n const queryClient = useQueryClient();\n const triggerRef = useRef<HTMLButtonElement>(null);\n const [open, setOpen] = useState(false);\n\n useEffect(() => {\n setOpen(menu !== null);\n }, [menu]);\n\n if (!menu) return null;\n const name = menu.path.split(\"/\").pop() || menu.path;\n\n function handleOpenChange(next: boolean) {\n setOpen(next);\n if (!next) closeFileMenu();\n }\n\n async function download() {\n if (!menu) return;\n closeFileMenu();\n try {\n await saveToDisk(menu.path);\n } catch (err) {\n window.alert(`Failed to download: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n async function remove() {\n if (!menu) return;\n const kind = menu.isDirectory ? \"folder (and everything inside it)\" : \"file\";\n if (!window.confirm(`Permanently delete this ${kind}? \"${name}\" — this cannot be undone.`)) {\n return closeFileMenu();\n }\n closeFileMenu();\n if (!client) {\n window.alert(\"Not connected — cannot delete remotely.\");\n return;\n }\n try {\n const response = await client.connection.request<{ ok: boolean; error?: string }>(\n \"file_delete_request\",\n { path: menu.path },\n );\n if (!response.ok) throw new Error(response.error ?? \"delete failed\");\n await queryClient.invalidateQueries({ queryKey: [\"explorer\"] });\n } catch (err) {\n window.alert(`Failed to delete: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n function startNew(kind: \"file\" | \"directory\") {\n if (!menu) return;\n const parent = menu.isDirectory\n ? menu.path\n : menu.path.split(\"/\").slice(0, -1).join(\"/\") || \"/\";\n closeFileMenu();\n startDraft(parent, kind);\n }\n\n return (\n <DropdownMenu.Root open={open} onOpenChange={handleOpenChange} modal={false}>\n <DropdownMenu.Trigger asChild>\n <button\n ref={triggerRef}\n type=\"button\"\n className={styles.trigger}\n style={{ left: menu.x, top: menu.y }}\n aria-hidden\n tabIndex={-1}\n />\n </DropdownMenu.Trigger>\n <DropdownMenu.Portal>\n <DropdownMenu.Content className={styles.content} align=\"start\" sideOffset={2}>\n <DropdownMenu.Item className={styles.item} onSelect={() => startNew(\"file\")}>\n New File\n </DropdownMenu.Item>\n <DropdownMenu.Item className={styles.item} onSelect={() => startNew(\"directory\")}>\n New Folder\n </DropdownMenu.Item>\n <DropdownMenu.Separator className={styles.sep} />\n {!menu.isDirectory && (\n <>\n <DropdownMenu.Item className={styles.item} onSelect={download}>\n Download\n </DropdownMenu.Item>\n <DropdownMenu.Separator className={styles.sep} />\n </>\n )}\n <DropdownMenu.Item className={`${styles.item} ${styles.danger}`} onSelect={remove}>\n Delete\n </DropdownMenu.Item>\n </DropdownMenu.Content>\n </DropdownMenu.Portal>\n </DropdownMenu.Root>\n );\n}\n","/**\n * Shared `file_create_request` caller for the two creation affordances (workspace-picker\n * \"new folder\" and the Files tree's inline \"new file\"/\"new folder\" draft row) — one place for\n * the wire call and the server error-code → user-facing message mapping.\n */\n\nimport type { PiStudioClient } from \"@av-pi-studio/client\";\n\nexport type CreateEntryKind = \"file\" | \"directory\";\n\nconst ERROR_MESSAGES: Record<string, string> = {\n empty_path: \"Choose a folder first.\",\n invalid_name: 'Invalid name — cannot be empty or contain \"/\".',\n exists: \"An item with that name already exists here.\",\n not_found: \"That folder no longer exists.\",\n not_a_directory: \"That path is not a folder.\",\n unreadable: \"That folder is not readable.\",\n};\n\n/** Create an empty file or a directory named `name` inside `parentPath`; returns its absolute path.\n * Throws an Error carrying a user-facing message on failure. */\nexport async function createEntry(\n client: PiStudioClient,\n parentPath: string,\n name: string,\n kind: CreateEntryKind,\n): Promise<string> {\n const response = await client.connection.request<{ ok: boolean; path?: string; error?: string }>(\n \"file_create_request\",\n { path: parentPath, name, kind },\n );\n if (!response.ok) {\n const code = response.error ?? \"\";\n throw new Error(ERROR_MESSAGES[code] ?? code ?? \"Failed to create\");\n }\n return (\n response.path ?? (parentPath.endsWith(\"/\") ? `${parentPath}${name}` : `${parentPath}/${name}`)\n );\n}\n","/**\n * FileExplorer — right-sidebar Files tab, tree view (POC `loadFiles`/`renderRightPanel`,\n * chat.html ~line 1014-1053, POC_TO_APP_PLAN_UI.md §4.7 — superseded the original breadcrumb-\n * style single-directory browser with a lazy-loading tree). Each expanded directory fetches its\n * own listing (`useExplorerTree`, one `file_explorer_request` per expanded path); `file-tree.ts`\n * flattens root + expansion set + per-path listings into the ordered row list rendered here\n * through `@tanstack/react-virtual` (same virtualizer pattern as `Timeline.tsx`). Directory click\n * toggles expand/collapse in place; file click opens a file tab. A header row uploads local files\n * into the current directory (button or drag-and-drop, resolved from the row dropped onto, or the\n * workspace root otherwise); each row can be saved back to disk via its \"⋮\" menu. Uploads and\n * downloads ride the binary file-transfer frames (features/file-explorer-transfer.md).\n */\n\nimport { useEffect, useMemo, useRef, useState, type DragEvent } from \"react\";\nimport { useVirtualizer } from \"@tanstack/react-virtual\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { Upload, FilePlus, FolderPlus } from \"lucide-react\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { useExplorerStore, resolveTildePath } from \"@pi-studio-ui/stores/explorer-store.js\";\nimport { useExplorerTree } from \"@pi-studio-ui/hooks/use-explorer-tree.js\";\nimport { useFileTransfer } from \"@pi-studio-ui/hooks/use-file-transfer.js\";\nimport { useTabStore, tabIds } from \"@pi-studio-ui/stores/tab-store.js\";\nimport { useUiStore } from \"@pi-studio-ui/stores/ui-store.js\";\nimport { rpcKeys } from \"@pi-studio-ui/lib/connection/rpc-keys.js\";\nimport { flattenTree } from \"./file-tree.js\";\nimport { TreeNode } from \"./TreeNode.js\";\nimport { FileContextMenu } from \"./FileContextMenu.js\";\nimport { createEntry } from \"./create-entry.js\";\nimport styles from \"./FileExplorer.module.css\";\n\nconst ROW_HEIGHT_PX = 24;\n\nexport function FileExplorer() {\n const client = useConnectionStore((s) => s.client);\n const rootPath = useExplorerStore((s) => s.rootPath);\n const expanded = useExplorerStore((s) => s.expanded);\n const setRoot = useExplorerStore((s) => s.setRoot);\n const toggle = useExplorerStore((s) => s.toggle);\n const draft = useExplorerStore((s) => s.draft);\n const startDraft = useExplorerStore((s) => s.startDraft);\n const cancelDraft = useExplorerStore((s) => s.cancelDraft);\n // The workspace currently in view in the tab strip drives what Files/Changes browse — same\n // authoritative signal every tab creation site uses (§4.7 follow-up: workspace-scoped tabs).\n const activeWorkspaceCwd = useTabStore((s) => s.activeWorkspaceCwd);\n const openTab = useTabStore((s) => s.open);\n const { upload } = useFileTransfer();\n const openFileMenu = useUiStore((s) => s.openFileMenu);\n const queryClient = useQueryClient();\n\n const [status, setStatus] = useState<{ text: string; error: boolean } | null>(null);\n const [dragging, setDragging] = useState(false);\n const [dropTargetDir, setDropTargetDir] = useState<string | null>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // Re-root whenever the active workspace changes, and seed it once on first connect.\n // `lastSeededCwd` distinguishes \"the active workspace changed\" from a re-render — only the\n // former reseeds (and restores that workspace's remembered `expanded` set, see explorer-store).\n const lastSeededCwd = useRef<string | null>(null);\n useEffect(() => {\n if (!client) return;\n const target = activeWorkspaceCwd || \"~\";\n if (lastSeededCwd.current === target) return;\n lastSeededCwd.current = target;\n void resolveTildePath(client, target).then((resolved) => setRoot(resolved));\n }, [client, activeWorkspaceCwd, setRoot]);\n\n const tree = useExplorerTree(expanded);\n const rows = useMemo(\n () => flattenTree(rootPath, expanded, tree, draft),\n [rootPath, expanded, tree, draft],\n );\n\n const virtualizer = useVirtualizer({\n count: rows.length,\n getScrollElement: () => scrollRef.current,\n estimateSize: () => ROW_HEIGHT_PX,\n overscan: 12,\n getItemKey: (index) => rows[index]?.path ?? index,\n });\n\n function handleOpenFile(path: string) {\n openTab({\n id: tabIds.file(path),\n kind: \"file\",\n label: path.split(\"/\").pop() || path,\n closable: true,\n data: { path },\n workspaceCwd: activeWorkspaceCwd || \"~\",\n });\n }\n\n async function submitDraft(parentPath: string, name: string) {\n const kind = draft?.kind ?? \"file\";\n cancelDraft();\n if (!client) return;\n try {\n const created = await createEntry(client, parentPath, name, kind);\n await queryClient.invalidateQueries({ queryKey: rpcKeys.explorer(parentPath) });\n setStatus({ text: `Created ${created}`, error: false });\n if (kind === \"file\") handleOpenFile(created);\n } catch (err) {\n setStatus({ text: err instanceof Error ? err.message : \"Failed to create\", error: true });\n }\n }\n\n async function uploadFiles(dir: string, files: File[]) {\n if (!files.length || !dir) return;\n const existingListing = tree.get(dir)?.listing;\n const existing = new Set(existingListing?.entries.map((e) => e.name) ?? []);\n const clashes = files.filter((f) => existing.has(f.name));\n if (clashes.length > 0) {\n const names = clashes.map((f) => f.name).join(\", \");\n if (\n !window.confirm(`Overwrite ${clashes.length > 1 ? \"these files\" : \"this file\"}? ${names}`)\n ) {\n return;\n }\n }\n\n setStatus({\n text: `Uploading ${files.length} file${files.length > 1 ? \"s\" : \"\"}…`,\n error: false,\n });\n try {\n for (const file of files) await upload(dir, file);\n setStatus({\n text: `Uploaded ${files.length} file${files.length > 1 ? \"s\" : \"\"}`,\n error: false,\n });\n } catch (e) {\n setStatus({\n text: `Upload failed: ${e instanceof Error ? e.message : \"unknown\"}`,\n error: true,\n });\n }\n }\n\n function handleDrop(e: DragEvent) {\n e.preventDefault();\n setDragging(false);\n const dir = dropTargetDir ?? rootPath;\n setDropTargetDir(null);\n const files = Array.from(e.dataTransfer.files);\n void uploadFiles(dir, files);\n }\n\n function handleDragOver(e: DragEvent) {\n if (!e.dataTransfer.types.includes(\"Files\")) return;\n e.preventDefault();\n setDragging(true);\n }\n\n if (!client) {\n return <div className={styles.emptyState}>Connect to browse files</div>;\n }\n\n return (\n <div\n className={styles.container}\n onDragOver={handleDragOver}\n onDragLeave={() => setDragging(false)}\n onDrop={handleDrop}\n >\n <div className={styles.header}>\n <button\n type=\"button\"\n className={styles.uploadButton}\n onClick={() => startDraft(rootPath, \"file\")}\n disabled={!rootPath}\n title=\"New file in workspace root\"\n >\n <FilePlus size={12} />\n </button>\n <button\n type=\"button\"\n className={styles.uploadButton}\n onClick={() => startDraft(rootPath, \"directory\")}\n disabled={!rootPath}\n title=\"New folder in workspace root\"\n >\n <FolderPlus size={12} />\n </button>\n <button\n type=\"button\"\n className={styles.uploadButton}\n onClick={() => fileInputRef.current?.click()}\n disabled={!rootPath}\n title=\"Upload files to the workspace root\"\n >\n <Upload size={12} />\n <span>Upload</span>\n </button>\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n className={styles.hiddenInput}\n onChange={(e) => {\n void uploadFiles(rootPath, Array.from(e.target.files ?? []));\n e.target.value = \"\";\n }}\n />\n </div>\n\n {status && (\n <div className={status.error ? styles.statusError : styles.status}>{status.text}</div>\n )}\n\n <div className={styles.list} ref={scrollRef}>\n {rows.length === 0 && <div className={styles.emptyState}>No files loaded</div>}\n <div style={{ position: \"relative\", height: virtualizer.getTotalSize() }}>\n {virtualizer.getVirtualItems().map((virtualRow) => {\n const row = rows[virtualRow.index];\n if (!row) return null;\n return (\n <div\n key={virtualRow.key}\n data-index={virtualRow.index}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: virtualRow.size,\n transform: `translateY(${virtualRow.start}px)`,\n }}\n onDragEnter={() => {\n if (row.kind === \"directory\") setDropTargetDir(row.path);\n }}\n >\n <TreeNode\n row={row}\n onToggle={toggle}\n onOpenFile={handleOpenFile}\n onContextMenu={(path, isDirectory, x, y) => openFileMenu(path, isDirectory, x, y)}\n onSubmitDraft={(parentPath, name) => void submitDraft(parentPath, name)}\n onCancelDraft={cancelDraft}\n />\n </div>\n );\n })}\n </div>\n </div>\n\n {dragging && <div className={styles.dropOverlay}>Drop to upload</div>}\n <FileContextMenu />\n </div>\n );\n}\n","/**\n * Git store — the right-sidebar Changes tab's checkout-status projection (POC `changeList`\n * global + `handleCheckoutStatusUpdate`, POC_TO_APP_PLAN_UI.md §4.7). `applyProjection` ports the\n * POC mapping verbatim: staged entries key off `indexStatus`, unstaged entries key off\n * `worktreeStatus`, untracked paths are always \"added\"/unstaged.\n *\n * Also retains the projection's branch metadata (branch/ahead/behind/detached/upstream/conflict\n * count/availability) for the workspace status bar's git segment (sprint-042) — previously\n * discarded here even though `use-checkout-status.ts` already receives the full projection.\n */\n\nimport { create } from \"zustand\";\n\nexport interface CheckoutFileEntry {\n path: string;\n indexStatus: string;\n worktreeStatus: string;\n}\n\nexport interface CheckoutStatusProjection {\n available: boolean;\n branch: string | null;\n upstream: string | null;\n ahead: number;\n behind: number;\n detached: boolean;\n staged: CheckoutFileEntry[];\n unstaged: CheckoutFileEntry[];\n untracked: string[];\n conflicted: string[];\n hasConflicts: boolean;\n unavailableReason?: string;\n}\n\nexport interface ChangeEntry {\n path: string;\n status: \"added\" | \"deleted\" | \"modified\";\n staged: boolean;\n}\n\n/** Branch metadata reset when a cwd isn't a git repo (or has no projection yet). */\nconst EMPTY_BRANCH_META = {\n available: false,\n branch: null as string | null,\n upstream: null as string | null,\n ahead: 0,\n behind: 0,\n detached: false,\n conflictCount: 0,\n};\n\ninterface GitStoreState {\n subscribedCwd: string | null;\n changes: ChangeEntry[];\n available: boolean;\n branch: string | null;\n upstream: string | null;\n ahead: number;\n behind: number;\n detached: boolean;\n conflictCount: number;\n\n setSubscribedCwd(cwd: string | null): void;\n /** Map a `checkout_status_update` projection into flat change rows (POC `handleCheckoutStatusUpdate`)\n * plus the branch metadata above. */\n applyProjection(projection: CheckoutStatusProjection | null | undefined): void;\n}\n\nexport const useGitStore = create<GitStoreState>()((set) => ({\n subscribedCwd: null,\n changes: [],\n ...EMPTY_BRANCH_META,\n\n setSubscribedCwd: (cwd) => set({ subscribedCwd: cwd }),\n\n applyProjection: (projection) => {\n if (!projection || !projection.available) {\n set({ changes: [], ...EMPTY_BRANCH_META });\n return;\n }\n\n const changes: ChangeEntry[] = [];\n for (const f of projection.staged) {\n changes.push({\n path: f.path,\n status: f.indexStatus === \"A\" ? \"added\" : f.indexStatus === \"D\" ? \"deleted\" : \"modified\",\n staged: true,\n });\n }\n for (const f of projection.unstaged) {\n changes.push({\n path: f.path,\n status: f.worktreeStatus === \"D\" ? \"deleted\" : \"modified\",\n staged: false,\n });\n }\n for (const path of projection.untracked) {\n changes.push({ path, status: \"added\", staged: false });\n }\n set({\n changes,\n available: true,\n branch: projection.branch,\n upstream: projection.upstream,\n ahead: projection.ahead,\n behind: projection.behind,\n detached: projection.detached,\n conflictCount: projection.conflicted.length,\n });\n },\n}));\n","/**\n * ChangesPanel — right-sidebar Changes tab (POC `renderRightPanel` changes branch, chat.html\n * ~line 1028-1039, POC_TO_APP_PLAN_UI.md §4.7). A/M/D badges + file path; click opens a diff tab.\n * A pure `git-store` consumer — the live checkout-status subscription for the active workspace is\n * owned solely by `StatusBar` (sprint-042; it is always mounted, unlike this panel, which only\n * exists while the Changes tab is selected). Two independent `useCheckoutStatus(cwd)` callers on\n * the same cwd would collide: the daemon's `checkout_status_subscribe`/`_unsubscribe` handlers key\n * on a flat `session:cwd` map (`packages/server/src/projects/git-checkout-rpc.ts`), not\n * reference-counted, so whichever hook instance unmounts first (e.g. switching away from this\n * tab) would silently kill the *other* instance's live feed too.\n */\n\nimport { clsx } from \"clsx\";\nimport { useGitStore } from \"@pi-studio-ui/stores/git-store.js\";\nimport { useTabStore, tabIds } from \"@pi-studio-ui/stores/tab-store.js\";\nimport styles from \"./ChangesPanel.module.css\";\n\nconst BADGE_LABEL: Record<\"added\" | \"modified\" | \"deleted\", string> = {\n added: \"A\",\n modified: \"M\",\n deleted: \"D\",\n};\n\nexport function ChangesPanel() {\n const activeWorkspaceCwd = useTabStore((s) => s.activeWorkspaceCwd);\n const cwd = activeWorkspaceCwd || \"~\";\n\n const changes = useGitStore((s) => s.changes);\n const openTab = useTabStore((s) => s.open);\n\n function handleClick(path: string, staged: boolean) {\n openTab({\n id: tabIds.diff(path, staged),\n kind: \"diff\",\n label: path.split(\"/\").pop() || path,\n closable: true,\n data: { path, staged },\n workspaceCwd: cwd,\n });\n }\n\n if (changes.length === 0) {\n return <div className={styles.emptyState}>No changes detected</div>;\n }\n\n return (\n <div className={styles.list}>\n <div className={styles.summary}>\n {changes.length} changed file{changes.length === 1 ? \"\" : \"s\"}\n </div>\n {changes.map((change, i) => (\n <div\n key={`${change.path}-${change.staged}-${i}`}\n className={styles.item}\n onClick={() => handleClick(change.path, change.staged)}\n >\n <span className={clsx(styles.badge, styles[change.status])}>\n {BADGE_LABEL[change.status]}\n </span>\n <span className={styles.name}>{change.path}</span>\n </div>\n ))}\n </div>\n );\n}\n","/**\n * RightSidebar — Files/Changes tab bar + refresh button (POC `.sidebar-right`, chat.html\n * ~line 364-374, POC_TO_APP_PLAN_UI.md §4.7). Imported by `routes/WorkspacePage.tsx`. Used to\n * also host a Terminals tab (live PTY list + reattach/kill) — removed once the TabStrip's \"+\"\n * menu (New chat / New terminal) made per-tab terminal creation directly discoverable, leaving\n * the sidebar entry redundant (GitHub issue #8 follow-up).\n */\n\nimport { RotateCw } from \"lucide-react\";\nimport { clsx } from \"clsx\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { Button } from \"@pi-studio-ui/components/primitives/Button.js\";\nimport { useUiStore } from \"@pi-studio-ui/stores/ui-store.js\";\nimport { useTabStore } from \"@pi-studio-ui/stores/tab-store.js\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { FileExplorer } from \"./FileExplorer.js\";\nimport { ChangesPanel } from \"@pi-studio-ui/features/git/ChangesPanel.js\";\nimport styles from \"./RightSidebar.module.css\";\n\nexport function RightSidebar() {\n const rightSidebarTab = useUiStore((s) => s.rightSidebarTab);\n const setRightSidebarTab = useUiStore((s) => s.setRightSidebarTab);\n // Files/Changes browse the workspace currently in view in the tab strip — the same signal\n // every tab creation site uses (§4.7 follow-up: workspace-scoped tabs).\n const activeWorkspaceCwd = useTabStore((s) => s.activeWorkspaceCwd);\n const cwd = activeWorkspaceCwd || \"~\";\n const client = useConnectionStore((s) => s.client);\n const queryClient = useQueryClient();\n\n // Fires a one-shot refresh RPC (POC `checkout_refresh_request`) — subscription lifecycle for\n // live `checkout_status_update` pushes is owned solely by `StatusBar`'s `useCheckoutStatus`\n // (sprint-042: promoted from `ChangesPanel` since `StatusBar` is always mounted), so this\n // button never opens a second subscription that could outlive/race it.\n function handleRefresh() {\n void queryClient.invalidateQueries({ queryKey: [\"explorer\"] });\n if (client && cwd) {\n void client.connection.request(\"checkout_refresh_request\", { cwd }).catch(() => {});\n }\n }\n return (\n <div className={styles.wrap}>\n <div className={styles.tabBar}>\n <button\n type=\"button\"\n className={clsx(styles.tabBtn, rightSidebarTab === \"files\" && styles.active)}\n onClick={() => setRightSidebarTab(\"files\")}\n >\n Files\n </button>\n <button\n type=\"button\"\n className={clsx(styles.tabBtn, rightSidebarTab === \"changes\" && styles.active)}\n onClick={() => setRightSidebarTab(\"changes\")}\n >\n Changes\n </button>\n <Button\n size=\"xs\"\n variant=\"ghost\"\n iconOnly\n title=\"Refresh\"\n className={styles.refreshBtn}\n onClick={handleRefresh}\n >\n <RotateCw size={13} />\n </Button>\n </div>\n <div className={styles.content}>\n {rightSidebarTab === \"files\" ? <FileExplorer /> : <ChangesPanel />}\n </div>\n </div>\n );\n}\n","/**\n * Stats store — per-session context/token/cost usage + poll-reconciled model, for the workspace\n * status bar (sprint-042). Sourced from `agent_session_stats_request` (SDK\n * `client.agent(id).sessionStats()`), which is pull-only: no stream event carries this data (see\n * `use-session-stats.ts`, which polls it). Keyed by **sessionId**, not `agentId` — a fresh session\n * has no `agentId` yet, and keying by sessionId lets switching back to a previously-visited\n * session show its last-known stats instantly instead of a blank flash while a fresh poll runs.\n */\n\nimport { create } from \"zustand\";\n\nexport interface SessionStats {\n contextTokens?: number;\n contextWindow?: number;\n /** 0–1 fraction (matches the wire `agentContextUsageSchema.percent` convention). */\n contextPercent?: number;\n totalTokens?: number;\n inputTokens?: number;\n outputTokens?: number;\n cost?: number;\n /** Poll-reconciled model id — see `agent_session_stats_response.payload.model` (sprint-042). */\n model?: string;\n}\n\ninterface StatsStoreState {\n bySession: Record<string, SessionStats>;\n\n /** Shallow-merge a partial update into one session's stats (missing keys are left untouched). */\n setStats(sessionId: string, partial: Partial<SessionStats>): void;\n clear(sessionId: string): void;\n}\n\nexport const useStatsStore = create<StatsStoreState>()((set) => ({\n bySession: {},\n\n setStats(sessionId, partial) {\n set((s) => ({\n bySession: {\n ...s.bySession,\n [sessionId]: { ...s.bySession[sessionId], ...partial },\n },\n }));\n },\n\n clear(sessionId) {\n set((s) => {\n const { [sessionId]: _removed, ...rest } = s.bySession;\n return { bySession: rest };\n });\n },\n}));\n","/**\n * `use-session-stats` — polls `agent_session_stats_request` (SDK `client.agent(id).sessionStats()`)\n * for one session's context/token/cost usage and poll-reconciled model, writing into `stats-store`\n * (sprint-042). This data is **pull-only**: no `AgentStreamEvent` kind carries context/token/cost\n * (see `agentStreamEventSchema` in `@av-pi-studio/protocol`), so there is no push channel to react\n * to — a poll is the only option.\n *\n * Poll triggers:\n * - once when a session with a bound `agentId` becomes the argument (mount / session switch /\n * agent bound after create),\n * - every `POLL_INTERVAL_MS` as a backstop while that stays true,\n * - immediately when the session's `status` transitions away from `\"running\"` — a proxy for\n * `turn_completed`/`turn_failed`/`turn_canceled` that avoids a second raw `agent_stream`\n * subscription: `agent-stream-events.ts` already drives exactly that status transition off those\n * stream events, so watching `SessionEntry.status` here is equivalent and simpler.\n *\n * A session with no `agentId` yet (a fresh, never-run chat) polls nothing — there is no agent to\n * ask. Failures (unsupported provider, no live session right now) are swallowed; the previous\n * cached value in `stats-store` is left untouched rather than cleared, so a transient failure\n * never blanks the status bar.\n */\n\nimport { useEffect, useRef } from \"react\";\nimport type { AgentSessionStatsResponse, AgentStatus } from \"@av-pi-studio/protocol\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { useSessionStore } from \"@pi-studio-ui/stores/session-store.js\";\nimport { useStatsStore, type SessionStats } from \"@pi-studio-ui/stores/stats-store.js\";\n\nconst POLL_INTERVAL_MS = 12_000;\n\ntype SessionStatus = AgentStatus | \"idle\" | undefined;\n\n/** True exactly when a turn just ended: `prev` was `\"running\"` and `next` no longer is. Pure —\n * exported for direct unit testing without rendering the hook. */\nexport function shouldRepollOnStatusChange(prev: SessionStatus, next: SessionStatus): boolean {\n return prev === \"running\" && next !== \"running\";\n}\n\n/** Maps the RPC payload onto `stats-store`'s flatter `SessionStats` shape, writing only fields the\n * payload actually carries — `setStats`'s merge treats an explicit `undefined` value as \"clear\n * this field\", not \"leave it alone\" (object spread doesn't distinguish an absent key from one set\n * to `undefined`), so a poll response that omits e.g. `cost` must never wipe a previously known\n * cost. Also reconciles `session-store`'s `model` — the status bar's model *segment* reads\n * `SessionEntry.model` (seeded by create/restore, updated live by `agent_update`), not\n * `stats-store`, so the poll must write through to keep that value authoritative (design intent:\n * \"stats poll authoritative + agent_update for instant feedback\" — a payload `model` this session\n * never learned any other way, e.g. right after `/model` cycle, must still reach the segment).\n * Exported for direct unit testing without rendering the hook. */\nexport function applySessionStats(\n sessionId: string,\n payload: AgentSessionStatsResponse[\"payload\"],\n): void {\n const patch: Partial<SessionStats> = {};\n if (payload.contextUsage?.tokens != null) patch.contextTokens = payload.contextUsage.tokens;\n if (payload.contextUsage?.contextWindow !== undefined) {\n patch.contextWindow = payload.contextUsage.contextWindow;\n }\n if (payload.contextUsage?.percent != null) patch.contextPercent = payload.contextUsage.percent;\n if (payload.tokens?.total !== undefined) patch.totalTokens = payload.tokens.total;\n if (payload.tokens?.input !== undefined) patch.inputTokens = payload.tokens.input;\n if (payload.tokens?.output !== undefined) patch.outputTokens = payload.tokens.output;\n if (payload.cost !== undefined) patch.cost = payload.cost;\n if (payload.model !== undefined) {\n patch.model = payload.model;\n useSessionStore.getState().setModel(sessionId, payload.model);\n }\n useStatsStore.getState().setStats(sessionId, patch);\n}\n\nexport function useSessionStats(sessionId: string | null): void {\n const client = useConnectionStore((s) => s.client);\n const status = useConnectionStore((s) => s.status);\n const agentId = useSessionStore((s) =>\n sessionId ? s.sessions[sessionId]?.agentId ?? null : null,\n );\n const sessionStatus = useSessionStore((s) => (sessionId ? s.sessions[sessionId]?.status : undefined));\n const inFlightRef = useRef(false);\n const prevStatusRef = useRef<typeof sessionStatus>(undefined);\n\n // Initial poll (on mount / whenever the polled agent identity changes) + interval backstop.\n useEffect(() => {\n if (status !== \"open\" || !client || !sessionId || !agentId) return;\n\n let cancelled = false;\n const poll = async () => {\n if (inFlightRef.current) return;\n inFlightRef.current = true;\n try {\n const payload = await client.agent(agentId).sessionStats();\n if (!cancelled) applySessionStats(sessionId, payload);\n } catch {\n // Best-effort — leave the prior cached value in place.\n } finally {\n inFlightRef.current = false;\n }\n };\n\n void poll();\n const interval = setInterval(() => void poll(), POLL_INTERVAL_MS);\n return () => {\n cancelled = true;\n clearInterval(interval);\n };\n }, [status, client, sessionId, agentId]);\n\n // Extra immediate poll when a turn just ended (running -> anything else).\n useEffect(() => {\n const prevStatus = prevStatusRef.current;\n prevStatusRef.current = sessionStatus;\n if (!shouldRepollOnStatusChange(prevStatus, sessionStatus)) return;\n if (status !== \"open\" || !client || !sessionId || !agentId) return;\n inFlightRef.current = false; // a stale in-flight interval poll shouldn't block this one\n void client\n .agent(agentId)\n .sessionStats()\n .then((payload) => applySessionStats(sessionId, payload))\n .catch(() => {});\n }, [sessionStatus, status, client, sessionId, agentId]);\n}\n","/**\n * `use-checkout-status` — subscribes to live git checkout status for a cwd (POC `loadChanges` +\n * `handleCheckoutStatusUpdate`, POC_TO_APP_PLAN_UI.md §4.7 / §3 `hooks/use-checkout-status.ts`).\n * On `cwd` change: unsubscribes the previous cwd (`checkout_status_unsubscribe`), subscribes the\n * new one (`checkout_status_subscribe`), and routes pushed `checkout_status_update` session\n * messages into `git-store`. Cleans up both the RPC subscription and the message handler on\n * unmount/cwd-change — the POC leaked subscriptions on cwd change (§4.7).\n */\n\nimport { useEffect, useRef } from \"react\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { useGitStore, type CheckoutStatusProjection } from \"@pi-studio-ui/stores/git-store.js\";\n\ninterface CheckoutStatusUpdateMessage {\n type: \"checkout_status_update\";\n cwd: string;\n projection: CheckoutStatusProjection;\n}\n\nfunction isCheckoutStatusUpdate(msg: unknown): msg is CheckoutStatusUpdateMessage {\n return (\n typeof msg === \"object\" &&\n msg !== null &&\n \"type\" in msg &&\n msg.type === \"checkout_status_update\" &&\n \"cwd\" in msg &&\n typeof msg.cwd === \"string\" &&\n \"projection\" in msg &&\n typeof msg.projection === \"object\" &&\n msg.projection !== null\n );\n}\n\nexport interface UseCheckoutStatusResult {\n refresh(): void;\n}\n\nexport function useCheckoutStatus(cwd: string): UseCheckoutStatusResult {\n const client = useConnectionStore((s) => s.client);\n const applyProjection = useGitStore((s) => s.applyProjection);\n const setSubscribedCwd = useGitStore((s) => s.setSubscribedCwd);\n const clientRef = useRef(client);\n clientRef.current = client;\n\n useEffect(() => {\n if (!client || !cwd) return;\n\n let cancelled = false;\n void client.connection.request(\"checkout_status_subscribe\", { cwd }).catch(() => {});\n setSubscribedCwd(cwd);\n\n const unsubscribeMessages = client.connection.onSessionMessage((msg) => {\n if (cancelled) return;\n const raw: unknown = msg;\n if (!isCheckoutStatusUpdate(raw)) return;\n if (raw.cwd !== cwd) return;\n applyProjection(raw.projection);\n });\n\n void client.connection.request(\"checkout_refresh_request\", { cwd }).catch(() => {});\n\n return () => {\n cancelled = true;\n unsubscribeMessages();\n void client.connection.request(\"checkout_status_unsubscribe\", { cwd }).catch(() => {});\n setSubscribedCwd(null);\n };\n }, [client, cwd, applyProjection, setSubscribedCwd]);\n\n return {\n refresh() {\n const current = clientRef.current;\n if (!current || !cwd) return;\n void current.connection.request(\"checkout_refresh_request\", { cwd }).catch(() => {});\n },\n };\n}\n","/**\n * Pure, framework-free value formatters for the workspace status bar (sprint-042). No store/DOM\n * access — every function takes plain values and returns a display string, so each is directly\n * unit-testable without rendering anything (matches the project's `lib/`-style convention of\n * keeping core logic free of DOM dependencies).\n */\n\nconst PLACEHOLDER = \"--\";\n\n/** `undefined` → \"--\"; <1000 exact; ≥1000 → \"12.3k\"; ≥1_000_000 → \"1.2M\" (one decimal, trimmed). */\nexport function formatTokens(n: number | undefined): string {\n if (n === undefined) return PLACEHOLDER;\n if (n < 1000) return String(n);\n if (n < 1_000_000) return `${(n / 1000).toFixed(1)}k`;\n return `${(n / 1_000_000).toFixed(1)}M`;\n}\n\n/** Accepts either a 0–1 fraction or a 0–100 whole number (the wire's `agentContextUsageSchema\n * .percent` convention is not pinned to one or the other across providers) and normalizes to a\n * rounded integer percentage. `undefined`/`null` → \"--\". */\nexport function formatPercent(p: number | null | undefined): string {\n if (p === null || p === undefined) return PLACEHOLDER;\n const pct = p <= 1 ? p * 100 : p;\n return `${Math.round(pct)}%`;\n}\n\n/** `undefined` → \"--\"; small costs keep more precision (< $1 → 4 decimals), larger costs 2 dp. */\nexport function formatCost(c: number | undefined): string {\n if (c === undefined) return PLACEHOLDER;\n const dp = c < 1 ? 4 : 2;\n return `$${c.toFixed(dp)}`;\n}\n\n/** Collapses a leading `home` prefix to `~`. No-op when `home` is null/undefined or not a prefix\n * of `cwd`. Does not truncate/ellipsize — that's a CSS concern for the component to apply. */\nexport function formatCwd(cwd: string, home: string | null | undefined): string {\n if (!home) return cwd;\n if (cwd === home) return \"~\";\n if (cwd.startsWith(`${home}/`)) return `~${cwd.slice(home.length)}`;\n return cwd;\n}\n\n/** `\"↑2 ↓1\"` — omits a zero side; `\"\"` when both are zero (nothing to show). */\nexport function formatBranchMeta(ahead: number, behind: number): string {\n const parts: string[] = [];\n if (ahead > 0) parts.push(`↑${ahead}`);\n if (behind > 0) parts.push(`↓${behind}`);\n return parts.join(\" \");\n}\n","/**\n * `use-provider-models` — TanStack Query wrapper over `list_provider_models`\n * (`client.providers.listModels`, sprint-043). Read-through cached by `[provider]`: a\n * subsequent open of the model picker shows the cached list immediately (`isLoading: false`)\n * while a background refetch keeps it current, instead of re-showing a loading spinner every\n * time — mirrors `use-file-diff.ts`/`use-explorer.ts`'s existing RPC-query convention.\n */\n\nimport { useQuery } from \"@tanstack/react-query\";\nimport type { ProviderModel } from \"@av-pi-studio/client\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { rpcKeys } from \"@pi-studio-ui/lib/connection/rpc-keys.js\";\n\nexport function useProviderModels(provider: string, enabled = true) {\n const client = useConnectionStore((s) => s.client);\n\n return useQuery({\n queryKey: rpcKeys.providerModels(provider),\n queryFn: async (): Promise<ProviderModel[]> => {\n if (!client) throw new Error(\"not connected\");\n const res = await client.providers.listModels(provider);\n return res.models;\n },\n enabled: Boolean(client) && Boolean(provider) && enabled,\n });\n}\n","// Combobox — option building, filtering, and keyboard-navigation state machine.\n// ui-components.md § Inputs & form controls (Combobox)\n// The rendering surface (popover vs bottom-sheet) is handled by resolveOverlayMode\n// from platform/overlay.ts. This module covers the pure logic.\n\nexport type ComboboxOption<T> = {\n value: T;\n label: string;\n description?: string;\n kind?: string;\n disabled?: boolean;\n};\n\nexport type ComboboxState<T> = {\n query: string;\n highlightedIndex: number;\n isOpen: boolean;\n options: ComboboxOption<T>[];\n /** Filtered options (derived from options + query). */\n filtered: ComboboxOption<T>[];\n};\n\n// ---------------------------------------------------------------------------\n// Option filtering\n// ---------------------------------------------------------------------------\n\n/**\n * Filter options by a search query (case-insensitive substring match on label\n * and optional description).\n */\nexport function filterOptions<T>(\n options: ComboboxOption<T>[],\n query: string,\n): ComboboxOption<T>[] {\n if (query.trim() === \"\") return options;\n const q = query.toLowerCase();\n return options.filter(\n (o) =>\n o.label.toLowerCase().includes(q) ||\n (o.description !== undefined && o.description.toLowerCase().includes(q)),\n );\n}\n\n// ---------------------------------------------------------------------------\n// Keyboard navigation\n// ---------------------------------------------------------------------------\n\nexport type ComboboxAction =\n | { type: \"OPEN\" }\n | { type: \"CLOSE\" }\n | { type: \"SET_QUERY\"; query: string }\n | { type: \"ARROW_DOWN\" }\n | { type: \"ARROW_UP\" }\n | { type: \"SELECT_HIGHLIGHTED\" }\n | { type: \"SELECT_INDEX\"; index: number };\n\n/**\n * Pure reducer for the combobox keyboard-navigation state machine.\n * Callers integrate this with their own state management.\n */\nexport function comboboxReducer<T>(\n state: ComboboxState<T>,\n action: ComboboxAction,\n): ComboboxState<T> {\n switch (action.type) {\n case \"OPEN\":\n return { ...state, isOpen: true, highlightedIndex: 0 };\n\n case \"CLOSE\":\n return { ...state, isOpen: false, query: \"\", filtered: state.options };\n\n case \"SET_QUERY\": {\n const filtered = filterOptions(state.options, action.query);\n return {\n ...state,\n query: action.query,\n filtered,\n // Reset highlight when query changes; clamp to new filtered length.\n highlightedIndex: filtered.length > 0 ? 0 : -1,\n };\n }\n\n case \"ARROW_DOWN\": {\n if (!state.isOpen) return { ...state, isOpen: true, highlightedIndex: 0 };\n const count = state.filtered.length;\n if (count === 0) return state;\n // Skip disabled options.\n let next = (state.highlightedIndex + 1) % count;\n let guard = 0;\n while (state.filtered[next]?.disabled && guard < count) {\n next = (next + 1) % count;\n guard++;\n }\n return { ...state, highlightedIndex: next };\n }\n\n case \"ARROW_UP\": {\n if (!state.isOpen) return { ...state, isOpen: true, highlightedIndex: 0 };\n const count = state.filtered.length;\n if (count === 0) return state;\n let prev = (state.highlightedIndex - 1 + count) % count;\n let guard = 0;\n while (state.filtered[prev]?.disabled && guard < count) {\n prev = (prev - 1 + count) % count;\n guard++;\n }\n return { ...state, highlightedIndex: prev };\n }\n\n case \"SELECT_HIGHLIGHTED\": {\n // The actual selection callback is handled by the component; the reducer\n // just closes (or stays open if keepOpenOnSelect).\n return { ...state, isOpen: false, query: \"\", filtered: state.options };\n }\n\n case \"SELECT_INDEX\": {\n const idx = action.index;\n if (idx < 0 || idx >= state.filtered.length) return state;\n return { ...state, highlightedIndex: idx, isOpen: false, query: \"\", filtered: state.options };\n }\n }\n}\n\n/** Build initial combobox state for a given options list. */\nexport function initialComboboxState<T>(options: ComboboxOption<T>[]): ComboboxState<T> {\n return {\n query: \"\",\n highlightedIndex: 0,\n isOpen: false,\n options,\n filtered: options,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Custom-value option building\n// ---------------------------------------------------------------------------\n\n/**\n * If `allowCustomValue` is true and the query doesn't match any existing option\n * exactly (case-insensitive), prepend a synthetic \"create\" option to the filtered\n * list with the query string as the value.\n */\nexport function withCustomValueOption(\n filtered: ComboboxOption<string>[],\n query: string,\n prefix = \"Create\",\n): ComboboxOption<string>[] {\n if (query.trim() === \"\") return filtered;\n const q = query.toLowerCase();\n const exactMatch = filtered.some((o) => o.label.toLowerCase() === q);\n if (exactMatch) return filtered;\n const synthetic: ComboboxOption<string> = {\n value: query,\n label: `${prefix} \"${query}\"`,\n kind: \"custom\",\n };\n return [synthetic, ...filtered];\n}\n","/**\n * Pure ordering helper for `ModelMenu` (sprint-043) — kept out of the `.tsx` file so it's\n * unit-testable under the root vitest config (which only discovers `.test.ts` files under a node\n * environment; `.tsx` component files are not picked up there).\n */\n\nexport interface ModelLike {\n id: string;\n}\n\n/** Move the model whose `id` matches `currentModel` to the front; keep the rest in server order. */\nexport function sortCurrentFirst<T extends ModelLike>(models: T[], currentModel?: string): T[] {\n if (!currentModel) return models;\n const current = models.find((m) => m.id === currentModel);\n if (!current) return models;\n return [current, ...models.filter((m) => m.id !== currentModel)];\n}\n\n/**\n * Drop later entries that repeat an earlier `id`. Real provider model lists can list the same\n * underlying model twice under different display groupings (observed live against the `pi`\n * provider: two entries both reporting id `claude-sonnet-5`) — `id` is what selection and React's\n * list `key` both rely on, so a duplicate silently causes a key collision and a stale/misplaced\n * row once the list is filtered. Call after `sortCurrentFirst` so, if the current model itself has\n * a duplicate elsewhere in the list, the kept occurrence is the one already sorted to the front.\n */\nexport function dedupeById<T extends ModelLike>(models: T[]): T[] {\n const seen = new Set<string>();\n return models.filter((m) => {\n if (seen.has(m.id)) return false;\n seen.add(m.id);\n return true;\n });\n}\n","/**\n * ModelMenu — model-selector trigger + anchored searchable picker (sprint-043; moved from the\n * composer into the workspace `StatusBar` footer). Renders a caller-supplied trigger element\n * (`renderTrigger`, e.g. `StatusBar.tsx`'s icon+text segment button) that opens an anchored\n * dropdown listing the provider's models with a fuzzy search filter at the top. The current\n * model sorts first with a checkmark; every row renders `label (id)` with the id in muted text.\n *\n * Anchoring follows `TabStrip.tsx`'s `NewTabMenu` visible-trigger pattern (`DropdownMenu.Trigger\n * asChild` wrapping a real element), NOT `SessionContextMenu`'s invisible fixed-coordinate\n * trigger (that pattern is for right-click menus only). Search reuses the pure `filterOptions`\n * helper (case-insensitive substring on label + id) instead of a bespoke filter.\n */\n\nimport { useEffect, useRef, useState, type KeyboardEvent, type ReactNode } from \"react\";\nimport * as DropdownMenu from \"@radix-ui/react-dropdown-menu\";\nimport { Check } from \"lucide-react\";\nimport { Spinner } from \"@pi-studio-ui/components/primitives/Spinner.js\";\nimport { useProviderModels } from \"@pi-studio-ui/hooks/use-provider-models.js\";\nimport { filterOptions, type ComboboxOption } from \"@pi-studio-ui/ui/combobox.js\";\nimport { sortCurrentFirst, dedupeById } from \"./model-menu-sort.js\";\nimport styles from \"./ModelMenu.module.css\";\n\nexport interface ModelMenuProps {\n /** `session.model` — undefined for a brand-new session with no known model yet. */\n currentModel?: string;\n provider: string;\n /**\n * `modelProvider` is the model's OWN underlying LLM provider (e.g. `\"anthropic\"`) — REQUIRED\n * by `client.agent(id).setModel(provider, modelId)`'s `provider` argument. Never hardcode the\n * pi-studio provider (`\"pi\"`) here: Pi has no model registered under a provider literally\n * named \"pi\" (sprint-043's \"Model not found: pi/<modelId>\" bug).\n */\n onSelect: (modelId: string, modelProvider?: string) => void;\n /** Renders the visible trigger element (must forward ref/props via `DropdownMenu.Trigger\n * asChild` — a real `<button>`/`Button`, not a fragment). Receives `currentModel` so the\n * caller can render its own placeholder/label styling. */\n renderTrigger: (currentModel: string | undefined) => ReactNode;\n}\n\nexport function ModelMenu({ currentModel, provider, onSelect, renderTrigger }: ModelMenuProps) {\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState(\"\");\n const searchRef = useRef<HTMLInputElement>(null);\n\n // Read-through cached by `[provider]` (`use-provider-models.ts`): once a session's model list\n // has been fetched once, reopening the menu shows it immediately (`isLoading: false`) instead\n // of a spinner every time, while TanStack Query refetches it in the background so it stays\n // current. Only fetches while the menu is actually open (`enabled: open`), matching\n // `OpenWorkspaceDialog.tsx`'s `useExplorer(path, open && ...)` convention.\n const { data: models = [], isLoading, isError, error } = useProviderModels(provider, open);\n const sortedModels = dedupeById(sortCurrentFirst(models, currentModel));\n\n // Reset the search query and refocus the search input every time the menu opens.\n useEffect(() => {\n if (!open) return;\n setQuery(\"\");\n searchRef.current?.focus();\n }, [open]);\n\n const providerById = new Map(sortedModels.map((m) => [m.id, m.provider]));\n const options: ComboboxOption<string>[] = sortedModels.map((m) => ({\n value: m.id,\n label: m.label ?? m.id,\n description: m.id,\n }));\n const filtered = filterOptions(options, query);\n const errorMessage = error instanceof Error ? error.message : \"Failed to load models\";\n\n // Radix's DropdownMenu.Content applies roving-focus typeahead to its Items; stop the search\n // input's keystrokes from bubbling there so typing filters instead of jumping to a matching item.\n function handleSearchKeyDown(e: KeyboardEvent<HTMLInputElement>) {\n e.stopPropagation();\n if (e.key === \"Escape\") setOpen(false);\n }\n\n return (\n <DropdownMenu.Root open={open} onOpenChange={setOpen} modal={false}>\n <DropdownMenu.Trigger asChild>{renderTrigger(currentModel)}</DropdownMenu.Trigger>\n <DropdownMenu.Portal>\n <DropdownMenu.Content className={styles.content} align=\"start\" sideOffset={4}>\n <input\n ref={searchRef}\n className={styles.search}\n placeholder=\"Search models…\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n onKeyDown={handleSearchKeyDown}\n />\n {isLoading && (\n <div className={styles.state}>\n <Spinner size=\"sm\" />\n </div>\n )}\n {!isLoading && isError && <div className={styles.stateError}>{errorMessage}</div>}\n {!isLoading && !isError && filtered.length === 0 && (\n <div className={styles.state}>No models found</div>\n )}\n {!isLoading && !isError && filtered.length > 0 && (\n <div className={styles.list}>\n {filtered.map((opt) => (\n <DropdownMenu.Item\n key={opt.value}\n className={styles.item}\n onSelect={() => onSelect(opt.value, providerById.get(opt.value))}\n >\n <span className={styles.checkSlot} aria-hidden>\n {opt.value === currentModel && <Check size={14} />}\n </span>\n <span className={styles.label}>{opt.label}</span>\n <span className={styles.modelId}>({opt.value})</span>\n </DropdownMenu.Item>\n ))}\n </div>\n )}\n </DropdownMenu.Content>\n </DropdownMenu.Portal>\n </DropdownMenu.Root>\n );\n}\n","/**\n * StatusBar — full-width, 35px, y-centered powerline bar at the bottom of the workspace shell\n * (sprint-042). Renders icon-prefixed segments for the **active session**: model, cwd, git branch\n * (+ ahead/behind/dirty/conflict), context usage, token total, and cost. Fully swaps when\n * `activeSessionId` changes — every value is read from the active session's entry or from\n * per-session `stats-store`; nothing here is scoped to a specific tab. Mounting this component is\n * also what drives the active session's stats poll (`useSessionStats`, sprint-042/task-004) — no\n * other consumer of `stats-store` exists yet, so the poll runs exactly while the bar is on screen.\n *\n * The model segment (moved here from the composer) is interactive: it's always shown while a\n * session is active (even before a model is known, as a \"Model\" placeholder) and opens the same\n * `ModelMenu` searchable picker the composer used to host, via `renderTrigger` so it can match\n * this bar's segment styling instead of the composer's toolbar button.\n */\n\nimport type { ReactNode } from \"react\";\nimport {\n ChevronDown,\n ChevronRight,\n Coins,\n Cpu,\n DollarSign,\n Folder,\n GitBranch,\n Gauge,\n} from \"lucide-react\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { ensureMaterialized } from \"@pi-studio-ui/stores/materialize.js\";\nimport { useSessionStore } from \"@pi-studio-ui/stores/session-store.js\";\nimport { useTabStore } from \"@pi-studio-ui/stores/tab-store.js\";\nimport { useGitStore } from \"@pi-studio-ui/stores/git-store.js\";\nimport { useStatsStore } from \"@pi-studio-ui/stores/stats-store.js\";\nimport { useSessionStats } from \"@pi-studio-ui/hooks/use-session-stats.js\";\nimport { useCheckoutStatus } from \"@pi-studio-ui/hooks/use-checkout-status.js\";\nimport { useHomeDir } from \"@pi-studio-ui/hooks/use-home-dir.js\";\nimport {\n formatBranchMeta,\n formatCost,\n formatCwd,\n formatPercent,\n formatTokens,\n} from \"./status-bar-format.js\";\nimport { ModelMenu } from \"../chat/ModelMenu.js\";\nimport styles from \"./StatusBar.module.css\";\n\ninterface Segment {\n key: string;\n icon: ReactNode;\n text: string;\n title?: string;\n}\n\nexport function StatusBar() {\n const activeSessionId = useSessionStore((s) => s.activeSessionId);\n const session = useSessionStore((s) =>\n activeSessionId ? s.sessions[activeSessionId] : undefined,\n );\n const client = useConnectionStore((s) => s.client);\n const setModel = useSessionStore((s) => s.setModel);\n const homeDir = useHomeDir();\n // Own the same live checkout-status subscription `ChangesPanel` uses (POC pattern,\n // `use-checkout-status.ts`), keyed off the same `activeWorkspaceCwd` — so the branch segment\n // updates live even when the Changes tab has never been opened. Subscribing to the SAME cwd\n // `ChangesPanel` would use is intentional and safe (idempotent re-subscribe); subscribing to a\n // *different* cwd than the Changes panel would corrupt its file list, since `git-store` is one\n // global singleton — this must always track the workspace-level cwd, never a per-session one.\n const activeWorkspaceCwd = useTabStore((s) => s.activeWorkspaceCwd);\n useCheckoutStatus(activeWorkspaceCwd || \"\");\n const gitAvailable = useGitStore((s) => s.available);\n const branch = useGitStore((s) => s.branch);\n const ahead = useGitStore((s) => s.ahead);\n const behind = useGitStore((s) => s.behind);\n const detached = useGitStore((s) => s.detached);\n const conflictCount = useGitStore((s) => s.conflictCount);\n const dirtyCount = useGitStore((s) => s.changes.length);\n\n const stats = useStatsStore((s) => (activeSessionId ? s.bySession[activeSessionId] : undefined));\n\n useSessionStats(activeSessionId);\n\n const segments: Segment[] = [];\n\n if (session) {\n const cwdText = formatCwd(session.cwd, homeDir);\n segments.push({ key: \"cwd\", icon: <Folder size={13} />, text: cwdText, title: session.cwd });\n }\n\n if (gitAvailable) {\n const branchMeta = formatBranchMeta(ahead, behind);\n const parts = [detached ? \"(detached)\" : (branch ?? \"?\")];\n if (branchMeta) parts.push(branchMeta);\n if (dirtyCount > 0) parts.push(`●${dirtyCount}`);\n if (conflictCount > 0) parts.push(`⚠${conflictCount}`);\n segments.push({ key: \"branch\", icon: <GitBranch size={13} />, text: parts.join(\" \") });\n }\n\n if (session) {\n const contextText = `${formatPercent(stats?.contextPercent)} (${formatTokens(stats?.contextTokens)}/${formatTokens(stats?.contextWindow)})`;\n segments.push({ key: \"context\", icon: <Gauge size={13} />, text: contextText });\n\n segments.push({\n key: \"tokens\",\n icon: <Coins size={13} />,\n text: formatTokens(stats?.totalTokens),\n title: `in ${formatTokens(stats?.inputTokens)} / out ${formatTokens(stats?.outputTokens)}`,\n });\n\n segments.push({\n key: \"cost\",\n icon: <DollarSign size={13} />,\n text: formatCost(stats?.cost),\n });\n }\n\n /**\n * `modelProvider` is the model's OWN underlying LLM provider (e.g. `\"anthropic\"`) — REQUIRED by\n * `client.agent(id).setModel(provider, modelId)`'s `provider` argument. Never hardcode the\n * pi-studio provider id (\"pi\") here; Pi has no model registered under a provider literally\n * named \"pi\" (sprint-043's \"Model not found: pi/<modelId>\" bug). Mirrors the composer's former\n * `handleSelectModel` (moved here with the UI).\n *\n * Single path regardless of whether the session is already materialized: `ensureMaterialized`\n * is a no-op once bound (the common case now that `tab-store.ts` `openNewChat` materializes\n * eagerly) and otherwise awaits whatever in-flight materialize is already running — the\n * `setModel` optimistic pick two lines up already updated the entry that materialize reads\n * `config.model`/`config.modelProvider` from, so there is no dropped-pick race even when this\n * fires while the eager materialize is still in flight.\n */\n function handleSelectModel(modelId: string, modelProvider?: string): void {\n if (!activeSessionId) return;\n setModel(activeSessionId, modelId, modelProvider); // optimistic display pick either way\n if (!client || !modelProvider) return;\n void (async () => {\n const agentId = await ensureMaterialized(client, activeSessionId);\n await client.agent(agentId).setModel(modelProvider, modelId);\n })().catch(() => {\n // Same swallow-and-let-the-stream-be-the-source-of-truth convention as the composer's\n // `submit` catch — a rejected `agent_set_model_request` has no dedicated UI surface today.\n });\n }\n\n return (\n <div className={styles.statusBar}>\n {session && (\n <span className={styles.segmentGroup}>\n <ModelMenu\n currentModel={session.model}\n provider=\"pi\"\n onSelect={handleSelectModel}\n renderTrigger={(currentModel) => (\n <button\n type=\"button\"\n className={styles.modelSegment}\n disabled={!client}\n title={currentModel ? `Model: ${currentModel}` : \"Select model\"}\n >\n <span className={styles.icon}>\n <Cpu size={13} />\n </span>\n <span className={styles.text}>{currentModel ?? \"Model\"}</span>\n <ChevronDown size={12} className={styles.modelChevron} aria-hidden=\"true\" />\n </button>\n )}\n />\n </span>\n )}\n {segments.map((seg, i) => (\n <span key={seg.key} className={styles.segmentGroup}>\n {(Boolean(session) || i > 0) && (\n <ChevronRight size={12} className={styles.chevron} aria-hidden=\"true\" />\n )}\n <span className={styles.segment} title={seg.title}>\n <span className={styles.icon}>{seg.icon}</span>\n <span className={styles.text}>{seg.text}</span>\n </span>\n </span>\n ))}\n </div>\n );\n}\n","/**\n * Checkbox — accessible checkbox control.\n * ui-components.md § Inputs & form controls\n */\n\nimport { type InputHTMLAttributes, type ReactNode, forwardRef } from \"react\";\nimport { Check } from \"lucide-react\";\nimport styles from \"./Checkbox.module.css\";\n\nexport interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, \"type\"> {\n label?: ReactNode;\n checked?: boolean;\n}\n\nexport const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(\n function Checkbox({ label, checked = false, disabled, className, onChange, ...rest }, ref) {\n return (\n <label className={styles.wrapper} data-disabled={disabled || undefined}>\n <input\n ref={ref}\n type=\"checkbox\"\n className={styles.nativeInput}\n checked={checked}\n disabled={disabled}\n onChange={onChange}\n {...rest}\n />\n <span className={styles.box} data-checked={checked} aria-hidden>\n {checked && <Check size={12} className={styles.checkmark} />}\n </span>\n {label && <span>{label}</span>}\n </label>\n );\n },\n);\n","/**\n * Select + Combobox — form selection controls.\n * Select = native <select> styled for theme consistency.\n * Combobox = filterable text input + dropdown list, consuming sprint-012 combobox model.\n * ui-components.md § Inputs & form controls\n */\n\nimport {\n forwardRef,\n useState,\n useRef,\n useEffect,\n type SelectHTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { clsx } from \"clsx\";\nimport styles from \"./Select.module.css\";\nimport {\n filterOptions,\n comboboxReducer,\n initialComboboxState,\n type ComboboxState,\n type ComboboxOption,\n type ComboboxAction,\n} from \"@pi-studio-ui/ui/combobox.js\";\n\n// ---------------------------------------------------------------------------\n// Native Select\n// ---------------------------------------------------------------------------\n\nexport interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {\n options: { value: string; label: string }[];\n placeholder?: string;\n}\n\nexport const Select = forwardRef<HTMLSelectElement, SelectProps>(\n function Select({ options, placeholder, className, ...rest }, ref) {\n return (\n <select ref={ref} className={clsx(styles.select, className)} {...rest}>\n {placeholder && (\n <option value=\"\" disabled>\n {placeholder}\n </option>\n )}\n {options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n );\n },\n);\n\n// ---------------------------------------------------------------------------\n// Combobox\n// ---------------------------------------------------------------------------\n\nexport interface ComboboxProps {\n options: ComboboxOption<string>[];\n value?: string;\n onSelect?: (value: string) => void;\n placeholder?: string;\n className?: string;\n}\n\nexport function Combobox({\n options,\n value,\n onSelect,\n placeholder = \"Search…\",\n className,\n}: ComboboxProps) {\n const [state, setState] = useState<ComboboxState<string>>(() =>\n initialComboboxState(options),\n );\n const inputRef = useRef<HTMLInputElement>(null);\n\n function dispatch(action: ComboboxAction) {\n setState((prev) => comboboxReducer(prev, action));\n }\n\n function handleSelect(opt: ComboboxOption<string>) {\n onSelect?.(opt.value);\n dispatch({ type: \"CLOSE\" });\n }\n\n // Close on outside click\n const wrapperRef = useRef<HTMLDivElement>(null);\n useEffect(() => {\n function handleClick(e: MouseEvent) {\n if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) {\n dispatch({ type: \"CLOSE\" });\n }\n }\n document.addEventListener(\"mousedown\", handleClick);\n return () => document.removeEventListener(\"mousedown\", handleClick);\n }, []);\n\n return (\n <div ref={wrapperRef} className={clsx(styles.comboboxWrapper, className)}>\n <input\n ref={inputRef}\n className={styles.comboboxInput}\n value={state.query}\n placeholder={placeholder}\n onChange={(e) => dispatch({ type: \"SET_QUERY\", query: e.target.value })}\n onFocus={() => dispatch({ type: \"OPEN\" })}\n onKeyDown={(e) => {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n dispatch({ type: \"ARROW_DOWN\" });\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n dispatch({ type: \"ARROW_UP\" });\n } else if (e.key === \"Enter\" && state.isOpen) {\n e.preventDefault();\n const opt = state.filtered[state.highlightedIndex];\n if (opt) handleSelect(opt);\n } else if (e.key === \"Escape\") {\n dispatch({ type: \"CLOSE\" });\n }\n }}\n role=\"combobox\"\n aria-expanded={state.isOpen}\n aria-autocomplete=\"list\"\n />\n {state.isOpen && state.filtered.length > 0 && (\n <div className={styles.comboboxList} role=\"listbox\">\n {state.filtered.map((opt, i) => (\n <button\n key={opt.value}\n className={clsx(\n styles.comboboxOption,\n i === state.highlightedIndex && styles.comboboxOptionHighlighted,\n )}\n role=\"option\"\n aria-selected={i === state.highlightedIndex}\n onMouseDown={(e) => {\n e.preventDefault();\n handleSelect(opt);\n }}\n >\n {opt.label}\n </button>\n ))}\n </div>\n )}\n </div>\n );\n}\n","/**\n * Dialog — generic centered modal chrome (overlay + card + header/title/close + optional footer),\n * wrapping Radix `Dialog` (design-system.md § Overlays: `@radix-ui/react-dialog` for\n * dialogs/menus/tooltips/popovers). Every feature-specific modal (workspace picker today; rename/\n * confirm dialogs later) SHOULD build on this instead of hand-rolling `Dialog.Root/Portal/Overlay/\n * Content` again — only the body content and footer actions vary per use.\n */\n\nimport { type ReactNode } from \"react\";\nimport * as RadixDialog from \"@radix-ui/react-dialog\";\nimport { X } from \"lucide-react\";\nimport { clsx } from \"clsx\";\nimport styles from \"./Dialog.module.css\";\n\nexport interface DialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n title: string;\n /** Body content between the header and the optional footer. */\n children?: ReactNode;\n /** Sticky action row rendered under the body (e.g. Cancel/Confirm buttons). */\n footer?: ReactNode;\n /** Content card width; defaults to 500px (design-system.md § Overlays desktop dialog sizing). */\n width?: number | string;\n /** Extra class applied to the content card, for callers that need to tweak body sizing. */\n className?: string;\n /**\n * Omit the header bar's reserved height and float the close button over the body instead.\n * Use for content (e.g. an image lightbox) that should sit dead-center in the viewport rather\n * than be pushed down by an asymmetric top-only header. `title` remains as an accessible\n * (visually hidden) label for the dialog.\n */\n bare?: boolean;\n}\n\n/** Re-exported so callers can wrap a footer button in `<Dialog.Close asChild>` to auto-dismiss\n * without threading `onOpenChange(false)` through their own click handler. */\nexport const DialogClose = RadixDialog.Close;\n\nexport function Dialog({\n open,\n onOpenChange,\n title,\n children,\n footer,\n width,\n className,\n bare = false,\n}: DialogProps) {\n return (\n <RadixDialog.Root open={open} onOpenChange={onOpenChange}>\n <RadixDialog.Portal>\n <RadixDialog.Overlay className={styles.overlay} />\n <div className={styles.contentWrapper}>\n <RadixDialog.Content\n className={clsx(styles.dialog, bare && styles.bare, className)}\n style={width !== undefined ? { width } : undefined}\n >\n {bare ? (\n <RadixDialog.Title className={styles.visuallyHidden}>{title}</RadixDialog.Title>\n ) : (\n <div className={styles.header}>\n <RadixDialog.Title className={styles.title}>{title}</RadixDialog.Title>\n <RadixDialog.Close asChild>\n <button type=\"button\" className={styles.closeBtn} aria-label=\"Close\">\n <X size={16} />\n </button>\n </RadixDialog.Close>\n </div>\n )}\n <div className={clsx(styles.body, bare && styles.bareBody)}>{children}</div>\n {bare && (\n <RadixDialog.Close asChild>\n <button type=\"button\" className={styles.bareCloseBtn} aria-label=\"Close\">\n <X size={16} />\n </button>\n </RadixDialog.Close>\n )}\n {footer && <div className={styles.footer}>{footer}</div>}\n </RadixDialog.Content>\n </div>\n </RadixDialog.Portal>\n </RadixDialog.Root>\n );\n}\n","/**\n * ResizeHandle — thin draggable strip for resizing an adjacent sidebar (`WorkspacePage`'s\n * `sidebarLeft`/`sidebarRight`). Pointer-driven: `pointerdown` starts a drag, `pointermove`\n * reports the raw pixel delta since the last move, `pointerup` ends it. Width clamping and\n * persistence live in the caller (`ui-store.ts`'s `setLeftSidebarWidth`/`setRightSidebarWidth`) —\n * this component only ever reports deltas, never owns or reads the width itself.\n */\n\nimport { useCallback, useRef } from \"react\";\nimport { clsx } from \"clsx\";\nimport styles from \"./ResizeHandle.module.css\";\n\nexport interface ResizeHandleProps {\n /** Which sidebar this handle resizes. Left sidebar grows when dragged right (positive delta);\n * right sidebar shrinks when dragged right (its left edge moves right into the panel) — `side`\n * picks the correct sign so the caller only ever adds the raw delta to its current width. */\n side: \"left\" | \"right\";\n onResize(deltaX: number): void;\n className?: string;\n}\n\nexport function ResizeHandle({ side, onResize, className }: ResizeHandleProps) {\n const startXRef = useRef(0);\n\n const handlePointerMove = useCallback(\n (ev: PointerEvent) => {\n const raw = ev.clientX - startXRef.current;\n startXRef.current = ev.clientX;\n onResize(side === \"left\" ? raw : -raw);\n },\n [onResize, side],\n );\n\n const handlePointerUp = useCallback(\n (ev: PointerEvent) => {\n window.removeEventListener(\"pointermove\", handlePointerMove);\n window.removeEventListener(\"pointerup\", handlePointerUp);\n document.body.style.cursor = \"\";\n document.body.style.userSelect = \"\";\n (ev.target as HTMLElement | null)?.releasePointerCapture?.(ev.pointerId);\n },\n [handlePointerMove],\n );\n\n function handlePointerDown(ev: React.PointerEvent<HTMLDivElement>): void {\n ev.preventDefault();\n startXRef.current = ev.clientX;\n document.body.style.cursor = \"col-resize\";\n document.body.style.userSelect = \"none\";\n window.addEventListener(\"pointermove\", handlePointerMove);\n window.addEventListener(\"pointerup\", handlePointerUp);\n }\n\n return (\n <div\n className={clsx(styles.handle, className)}\n onPointerDown={handlePointerDown}\n role=\"separator\"\n aria-orientation=\"vertical\"\n />\n );\n}\n","/**\n * Shared \"open a workspace\" resolver for the folder-picker dialog: given a target cwd, either\n * activates the workspace's most recently active existing session, or creates a fresh one —\n * avoids piling up duplicate sessions when the user re-opens a folder they already have a chat\n * in. Distinct from `SessionList`'s \"+ New conversation\", which always starts a brand-new\n * session by design (POC_TO_APP_PLAN_UI.md §4.3 workspace grouping, §4.7 follow-up: open-workspace\n * dialog).\n */\n\nimport { useSessionStore } from \"@pi-studio-ui/stores/session-store.js\";\nimport { useTabStore, tabIds, openNewChat } from \"@pi-studio-ui/stores/tab-store.js\";\nimport { useUiStore } from \"@pi-studio-ui/stores/ui-store.js\";\nimport { groupSessionsByWorkspace } from \"./workspace-grouping.js\";\n\n/**\n * Resolve `cwd` (already tilde/absolute-normalized by the caller, e.g. via `resolveTildePath`) to\n * a session: reuses the workspace's most recent session if one already exists at that cwd,\n * otherwise creates a new one. Activates the session and opens/focuses its chat tab. Also seeds\n * `useUiStore().cwd` so a subsequent bare \"+ New conversation\" defaults to this workspace.\n *\n * `homeDir` MUST be the same value the sidebar's grouping uses (`useHomeDir()`), so this matches\n * the exact workspace bucket a tilde-form and absolute-form cwd would collapse into — reusing a\n * different normalization here would resurrect the tilde/absolute duplicate-workspace bug.\n */\nexport function openWorkspace(cwd: string, homeDir: string | null): void {\n const { order, sessions } = useSessionStore.getState();\n\n const groups = groupSessionsByWorkspace(order, sessions, homeDir);\n const existing = groups.find((g) => g.cwd === cwd);\n\n if (existing?.sessions[0]) {\n const sessionId = existing.sessions[0].id;\n useSessionStore.getState().activate(sessionId);\n const session = useSessionStore.getState().sessions[sessionId];\n useTabStore.getState().open({\n id: tabIds.chat(sessionId),\n kind: \"chat\",\n label: session?.title ?? \"New chat\",\n closable: true,\n data: { sessionId },\n workspaceCwd: cwd,\n });\n } else {\n openNewChat(cwd);\n }\n\n useUiStore.getState().setCwd(cwd);\n}\n","/**\n * OpenWorkspaceDialog — directory browser for opening a new workspace (evolved from the POC\n * `#cwd-overlay`, chat.html ~line 328-336, 1122-1141; POC_TO_APP_PLAN_UI.md §4.7 follow-up:\n * toolbar \"Open Workspace\" button). Built on the generic `Dialog` primitive\n * (`components/primitives/Dialog.tsx`) — this file owns only the address input + Go/Up buttons +\n * directory list body. \"Open\" resolves the selected path to a session via `openWorkspace()` —\n * reusing the workspace's most recent existing session if one is already open there, else\n * creating a new one — then closes.\n */\n\nimport { useEffect, useState } from \"react\";\nimport { ArrowUp, Folder, FolderPlus } from \"lucide-react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { Button, Dialog, DialogClose } from \"@pi-studio-ui/components/primitives/index.js\";\nimport { TextInput } from \"@pi-studio-ui/components/primitives/TextInput.js\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { rpcKeys } from \"@pi-studio-ui/lib/connection/rpc-keys.js\";\nimport { useUiStore } from \"@pi-studio-ui/stores/ui-store.js\";\nimport { useTabStore } from \"@pi-studio-ui/stores/tab-store.js\";\nimport { normalizeCwd } from \"@pi-studio-ui/features/sessions/workspace-grouping.js\";\nimport { useExplorer } from \"@pi-studio-ui/hooks/use-explorer.js\";\nimport { useHomeDir } from \"@pi-studio-ui/hooks/use-home-dir.js\";\nimport { openWorkspace } from \"@pi-studio-ui/features/sessions/open-workspace.js\";\nimport { createEntry } from \"@pi-studio-ui/features/files/create-entry.js\";\nimport styles from \"./OpenWorkspaceDialog.module.css\";\n\nexport function OpenWorkspaceDialog() {\n const open = useUiStore((s) => s.cwdPickerOpen);\n const closeDialog = useUiStore((s) => s.closeCwdPicker);\n const activeWorkspaceCwd = useTabStore((s) => s.activeWorkspaceCwd);\n const uiCwd = useUiStore((s) => s.cwd);\n const client = useConnectionStore((s) => s.client);\n const homeDir = useHomeDir();\n\n const [path, setPathState] = useState(\"/\");\n const [inputValue, setInputValue] = useState(\"/\");\n const [creating, setCreating] = useState(false);\n const [newName, setNewName] = useState(\"\");\n const [createError, setCreateError] = useState<string | null>(null);\n const [createBusy, setCreateBusy] = useState(false);\n const queryClient = useQueryClient();\n\n // Seed the picker at the workspace currently in view each time it opens. Falls back to the\n // `?cwd=` deep-link param / last-opened workspace (`ui-store.cwd`), then home.\n useEffect(() => {\n if (!open) return;\n const seed = normalizeCwd(activeWorkspaceCwd || uiCwd || \"~\", homeDir);\n setPathState(seed);\n setInputValue(seed);\n resetCreate();\n }, [open, client, activeWorkspaceCwd, uiCwd]);\n\n const { data, isLoading, isError, error } = useExplorer(path, open && Boolean(client));\n const dirEntries = (data?.entries ?? []).filter((e) => e.kind === \"directory\");\n\n function resetCreate() {\n setCreating(false);\n setNewName(\"\");\n setCreateError(null);\n }\n\n function navigate(next: string) {\n setPathState(next);\n setInputValue(next);\n resetCreate();\n }\n\n function handleUp() {\n navigate(path.split(\"/\").slice(0, -1).join(\"/\") || \"/\");\n }\n\n function handleGo() {\n navigate(inputValue.trim());\n }\n\n async function handleCreate() {\n const name = newName.trim();\n if (!name || !client || createBusy) return;\n setCreateBusy(true);\n setCreateError(null);\n try {\n const created = await createEntry(client, path, name, \"directory\");\n await queryClient.invalidateQueries({ queryKey: rpcKeys.explorer(path) });\n navigate(created); // also resets the draft state\n } catch (err) {\n setCreateError(err instanceof Error ? err.message : \"Failed to create folder\");\n } finally {\n setCreateBusy(false);\n }\n }\n\n function handleOpen() {\n openWorkspace(path, homeDir);\n closeDialog();\n }\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => !next && closeDialog()}\n title=\"Open Workspace\"\n footer={\n <>\n <DialogClose asChild>\n <Button size=\"sm\" variant=\"secondary\">\n Cancel\n </Button>\n </DialogClose>\n <Button size=\"sm\" onClick={handleOpen}>\n Open this folder\n </Button>\n </>\n }\n >\n <div className={styles.pathRow}>\n <Button size=\"xs\" variant=\"ghost\" iconOnly title=\"Up\" onClick={handleUp}>\n <ArrowUp size={13} />\n </Button>\n <TextInput\n className={styles.pathInput}\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n handleGo();\n }\n }}\n />\n <Button size=\"xs\" onClick={handleGo}>\n Go\n </Button>\n <Button\n size=\"xs\"\n variant=\"ghost\"\n iconOnly\n title=\"New folder\"\n disabled={!client || isError || creating}\n onClick={() => {\n setCreating(true);\n setNewName(\"\");\n setCreateError(null);\n }}\n >\n <FolderPlus size={13} />\n </Button>\n </div>\n {creating && (\n <>\n <div className={styles.createRow}>\n <Folder size={14} />\n <TextInput\n className={styles.createInput}\n autoFocus\n placeholder=\"New folder name\"\n value={newName}\n onChange={(e) => setNewName(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void handleCreate();\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n e.stopPropagation();\n resetCreate();\n }\n }}\n />\n <Button\n size=\"xs\"\n loading={createBusy}\n disabled={!newName.trim() || createBusy}\n onClick={() => void handleCreate()}\n >\n Create\n </Button>\n <Button size=\"xs\" variant=\"secondary\" onClick={resetCreate}>\n Cancel\n </Button>\n </div>\n {createError && <div className={styles.createError}>{createError}</div>}\n </>\n )}\n <div className={styles.list}>\n {isLoading ? (\n <div className={styles.emptyState}>Loading...</div>\n ) : isError ? (\n <div className={styles.emptyState}>\n Error: {error instanceof Error ? error.message : \"unknown error\"}\n </div>\n ) : dirEntries.length === 0 ? (\n <div className={styles.emptyState}>No subdirectories</div>\n ) : (\n dirEntries.map((entry) => (\n <div\n key={entry.name}\n className={styles.item}\n onClick={() => navigate(path === \"/\" ? `/${entry.name}` : `${path}/${entry.name}`)}\n >\n <Folder size={14} />\n <span className={styles.name}>{entry.name}</span>\n </div>\n ))\n )}\n </div>\n </Dialog>\n );\n}\n","/**\n * WorkspacePage — the 3-column shell (POC `.main`: sidebar-left / center / sidebar-right,\n * POC_TO_APP_PLAN_UI.md §3). Feature panels are mounted here once each lands. Either sidebar can\n * be hidden via the Toolbar's panel toggles (`ui-store.ts` `leftSidebarCollapsed`/\n * `rightSidebarCollapsed`) — collapsed just means the `aside` isn't rendered, so `TabPanelHost`'s\n * `.center` column simply grows to fill the freed width. Each visible sidebar is resizable via a\n * `ResizeHandle` on its inner edge, dragging `ui-store.ts`'s `leftSidebarWidth`/\n * `rightSidebarWidth` (clamped to `[MIN_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH]`) through the\n * delta-based `resizeLeftSidebar`/`resizeRightSidebar` actions.\n */\n\nimport { Toolbar } from \"@pi-studio-ui/features/connection/Toolbar.js\";\nimport { SessionList } from \"@pi-studio-ui/features/sessions/SessionList.js\";\nimport { TabStrip } from \"@pi-studio-ui/features/workspace/TabStrip.js\";\nimport { TabPanelHost } from \"@pi-studio-ui/features/workspace/TabPanelHost.js\";\nimport { RightSidebar } from \"@pi-studio-ui/features/files/RightSidebar.js\";\nimport { StatusBar } from \"@pi-studio-ui/features/workspace/StatusBar.js\";\nimport { OpenWorkspaceDialog } from \"@pi-studio-ui/features/workspace-picker/OpenWorkspaceDialog.js\";\nimport { ResizeHandle } from \"@pi-studio-ui/components/primitives/ResizeHandle.js\";\nimport { useUiStore } from \"@pi-studio-ui/stores/ui-store.js\";\nimport styles from \"./WorkspacePage.module.css\";\n\nexport function WorkspacePage() {\n const leftSidebarCollapsed = useUiStore((s) => s.leftSidebarCollapsed);\n const rightSidebarCollapsed = useUiStore((s) => s.rightSidebarCollapsed);\n const leftSidebarWidth = useUiStore((s) => s.leftSidebarWidth);\n const rightSidebarWidth = useUiStore((s) => s.rightSidebarWidth);\n const resizeLeftSidebar = useUiStore((s) => s.resizeLeftSidebar);\n const resizeRightSidebar = useUiStore((s) => s.resizeRightSidebar);\n\n return (\n <div className={styles.shell}>\n <Toolbar />\n <div className={styles.main}>\n {!leftSidebarCollapsed && (\n <aside className={styles.sidebarLeft} style={{ width: leftSidebarWidth }}>\n <SessionList />\n <ResizeHandle side=\"left\" className={styles.leftHandle} onResize={resizeLeftSidebar} />\n </aside>\n )}\n <div className={styles.center}>\n <TabStrip />\n <TabPanelHost />\n </div>\n {!rightSidebarCollapsed && (\n <aside className={styles.sidebarRight} style={{ width: rightSidebarWidth }}>\n <ResizeHandle side=\"right\" className={styles.rightHandle} onResize={resizeRightSidebar} />\n <RightSidebar />\n </aside>\n )}\n </div>\n <StatusBar />\n <OpenWorkspaceDialog />\n </div>\n );\n}\n","/**\n * Boot hook: seeds the toolbar (host/password/cwd) from URL params\n * (POC `?host=&password=&cwd=&connect=1`, POC_TO_APP_PLAN_UI.md §4.1) and optionally\n * auto-connects. Runs once on mount.\n *\n * Also supports opening the web-client directly from a pairing link\n * (`pi-studio daemon pair`'s QR/link, architecture/relay-e2ee.md § Pairing):\n * - `location.hash` already carrying `#offer=...` (the web-client's own URL was used as the\n * pairing link's `baseUrl`) — connects immediately, no `connect=1` needed, since a pairing\n * link's entire purpose is a one-click connect.\n * - `?pair=<url-encoded pairing link>` — for pasting a link whose `baseUrl` is the hosted\n * `app.pi-studio.sh` landing page into `?pair=` instead of the fragment directly (mirrors the\n * existing `?host=&connect=1` convention).\n * Either form is handed to `connect()` verbatim — it detects the pairing link itself via\n * `parsePairingUrl` and branches to the relay transport when the link carries one.\n */\n\nimport { useEffect, useRef } from \"react\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { useUiStore } from \"@pi-studio-ui/stores/ui-store.js\";\n\nexport function useConnectionBoot(): void {\n const ranRef = useRef(false);\n\n useEffect(() => {\n if (ranRef.current) return;\n ranRef.current = true;\n\n // A pairing link landed directly on this origin (hash carries `offer=`) — connect immediately.\n if (window.location.hash.includes(\"offer=\")) {\n const pairingUrl = window.location.href;\n useUiStore.getState().setHost(pairingUrl);\n void useConnectionStore.getState().connect({ url: pairingUrl });\n return;\n }\n\n const qs = new URLSearchParams(window.location.search);\n const host = qs.get(\"host\");\n const pair = qs.get(\"pair\");\n const password = qs.get(\"password\");\n const cwd = qs.get(\"cwd\");\n const shouldConnect = qs.get(\"connect\") === \"1\";\n\n const ui = useUiStore.getState();\n if (pair) ui.setHost(pair);\n else if (host) ui.setHost(host);\n if (password) ui.setPassword(password);\n if (cwd) ui.setCwd(cwd);\n\n if (pair) {\n // A pairing link carries its own credential (the daemon's public key) — connect\n // unconditionally, `connect=1` is only required for a plain host/password connection.\n void useConnectionStore.getState().connect({ url: pair });\n } else if (shouldConnect) {\n const { host: h, password: pw } = useUiStore.getState();\n void useConnectionStore.getState().connect({ url: h, password: pw || undefined });\n }\n }, []);\n}\n","/**\n * `fetch_agent_timeline_request` returns `items: unknown[]` (not yet in the strict protocol\n * union). The POC's `restoreSessions()` observed a stable shape at runtime: each item is\n * `{kind:'other'|'assistant'|'tool_call', event?, events?}` carrying nested `AgentStreamEvent`s.\n * This flattens that shape into a plain event list the timeline reducer can replay.\n */\n\nimport type { AgentStreamEvent } from \"@av-pi-studio/protocol\";\n\ninterface TimelineItemOther {\n kind: \"other\";\n event?: AgentStreamEvent;\n}\n\ninterface TimelineItemAssistant {\n kind: \"assistant\";\n events?: AgentStreamEvent[];\n}\n\ninterface TimelineItemToolCall {\n kind: \"tool_call\";\n events?: AgentStreamEvent[];\n}\n\ntype TimelineItem = TimelineItemOther | TimelineItemAssistant | TimelineItemToolCall;\n\nfunction isTimelineItem(value: unknown): value is TimelineItem {\n return typeof value === \"object\" && value !== null && \"kind\" in value;\n}\n\n/** Flatten authoritative timeline `items` into an ordered `AgentStreamEvent[]` for replay. */\nexport function flattenTimelineItems(items: readonly unknown[]): AgentStreamEvent[] {\n const events: AgentStreamEvent[] = [];\n for (const raw of items) {\n if (!isTimelineItem(raw)) continue;\n if (raw.kind === \"other\") {\n if (raw.event) events.push(raw.event);\n } else {\n for (const event of raw.events ?? []) events.push(event);\n }\n }\n return events;\n}\n","/**\n * Session restore — on a fresh `open` connection, fetches known agents\n * (`list_agents_request`) and hydrates each into the session store from its authoritative\n * timeline (`fetch_agent_timeline_request`). Ports POC `restoreSessions()`/`onConnected()`\n * (POC_TO_APP_PLAN_UI.md §4.3 \"Session restore\"). Live agent-stream subscription for restored\n * sessions is wired by `ChatPanel`'s `useAgentStream` once its tab mounts, keyed off `agentId` —\n * no `expectingAgent` race, since `agentId` is always known before subscribing.\n *\n * A daemon reporting zero agents restores nothing: `activeWorkspaceCwd` stays `null` and the UI\n * renders its no-workspace empty state (`TabPanelHost`) — this hook never auto-creates a session.\n * Every known agent restores, including an idle, never-used draft (eager materialization,\n * `materialize.ts`, `tab-store.ts`'s `openNewChat`) that survives to the next connect because its\n * tab was never explicitly closed — it is a real persisted session the instant it materializes,\n * not a phantom the UI should hide; only an explicit close (`closeTab`'s `discardIfEmpty`) or an\n * explicit delete removes it.\n */\n\nimport { useEffect, useRef } from \"react\";\nimport type { AgentUpdateMessage } from \"@av-pi-studio/client\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { useSessionStore } from \"@pi-studio-ui/stores/session-store.js\";\nimport { useTabStore, tabIds } from \"@pi-studio-ui/stores/tab-store.js\";\nimport { useUiStore } from \"@pi-studio-ui/stores/ui-store.js\";\nimport { resolveHome } from \"@pi-studio-ui/stores/explorer-store.js\";\nimport { normalizeCwd } from \"@pi-studio-ui/features/sessions/workspace-grouping.js\";\nimport { EMPTY_TIMELINE, applyStreamEvent } from \"@pi-studio-ui/timeline/reducer.js\";\nimport { flattenTimelineItems } from \"@pi-studio-ui/lib/protocol/events.js\";\nimport type { AgentStatus } from \"@av-pi-studio/protocol\";\n\ninterface RestoredAgent {\n agentId: string;\n status?: string;\n cwd?: string;\n title?: string;\n lastActivity?: number;\n /** Live model (sprint-042), sourced server-side from the attached session's runtime info when\n * one exists, else the persisted `record.config?.model` (deferred draft or daemon restart —\n * see `list_agents_request` in `daemon/bootstrap.ts`/`dev-bootstrap.ts`). */\n model?: string;\n /** The model's OWN underlying LLM provider (e.g. `\"anthropic\"`) — always sourced from\n * `record.config?.modelProvider` (no live-session override exists for this field; a session's\n * `getRuntimeInfo().provider` is the pi-studio `AgentClient` id, a different namespace — see\n * `provider-contract.ts` `ProviderRuntimeInfo`). REQUIRED to call `setModel` again after a\n * restore without falling into `handleSelectModel`'s `if (!modelProvider) return` no-op. */\n modelProvider?: string;\n provider?: string;\n}\n\n/** Exported for direct unit testing — this project's vitest config runs `.test.ts` under a plain\n * Node environment (no DOM), so hooks with effects are verified by smoke test (task-007) rather\n * than rendered; this narrow type guard is the one piece of the listener worth a direct test. */\nexport function hasStringModel(\n msg: AgentUpdateMessage,\n): msg is AgentUpdateMessage & { model: string } {\n return typeof msg.model === \"string\";\n}\n\nexport function useSessionRestore(): void {\n const client = useConnectionStore((s) => s.client);\n const status = useConnectionStore((s) => s.status);\n const restoredRef = useRef(false);\n\n useEffect(() => {\n if (status !== \"open\" || !client) return;\n if (restoredRef.current) return; // one restore per connection lifetime\n restoredRef.current = true;\n\n void (async () => {\n let agents: RestoredAgent[] = [];\n try {\n const result = await client.connection.request<{ agents?: RestoredAgent[] }>(\n \"list_agents_request\",\n { all: false },\n );\n agents = result.agents ?? [];\n } catch {\n agents = [];\n }\n\n agents.sort((a, b) => (b.lastActivity ?? 0) - (a.lastActivity ?? 0));\n\n const sessionStore = useSessionStore.getState();\n for (const agent of agents) {\n if (sessionStore.findByAgentId(agent.agentId)) continue;\n\n let timeline = EMPTY_TIMELINE;\n try {\n const page = await client.agent(agent.agentId).timeline.fetch({\n direction: \"after\",\n limit: 200,\n });\n const events = flattenTimelineItems(page.items);\n timeline = events.reduce(applyStreamEvent, EMPTY_TIMELINE);\n } catch {\n // Best-effort: an unreadable timeline still gets an (empty) restored session.\n }\n\n // A never-used draft (eager materialization, `materialize.ts`) has no server-persisted\n // title (only an explicit rename — `SessionContextMenu.tsx` — ever sets one) and no\n // messages to derive one from; default it to \"New chat\", matching a freshly\n // `createSession`-created entry, instead of falling through to the cwd basename.\n const isEmptyDraft = timeline.rows.length === 0;\n sessionStore.hydrate({\n id: `s-${agent.agentId.slice(0, 12)}`,\n agentId: agent.agentId,\n title:\n agent.title ||\n (isEmptyDraft ? \"New chat\" : agent.cwd?.split(\"/\").pop()) ||\n \"Restored\",\n status: (agent.status as AgentStatus | undefined) ?? \"idle\",\n cwd: agent.cwd || \"~\",\n timeline,\n userMessageCount: timeline.rows.filter((r) => r.kind === \"user\").length,\n model: agent.model,\n modelProvider: agent.modelProvider,\n });\n }\n\n const { order, sessions } = useSessionStore.getState();\n const first = order.length > 0 ? sessions[order[0] ?? \"\"] : undefined;\n // Zero restored sessions: leave `tab-store.activeWorkspaceCwd` null and let `TabPanelHost`\n // render its \"no workspace open\" state. NEVER auto-create a session here — a workspace\n // exists only because the user opened a folder (or a restored agent carries a cwd).\n if (!first) return;\n\n const homeDir = await resolveHome(client).catch(() => null);\n const targetCwd = normalizeCwd(first.cwd || \"~\", homeDir);\n useSessionStore.getState().activate(first.id);\n useUiStore.getState().setCwd(first.cwd);\n useTabStore.getState().open({\n id: tabIds.chat(first.id),\n kind: \"chat\",\n label: first.title,\n closable: true,\n data: { sessionId: first.id },\n workspaceCwd: targetCwd,\n });\n })();\n }, [status, client]);\n\n // Live model updates from an explicit `/model` set (sprint-042). `agent_update` is broadcast\n // by every RPC that changes agent state (create/status/model/…) and reaches `onSessionMessage`\n // because the daemon wraps every non-session-enveloped broadcast in `{type:\"session\", message}`\n // (`wrapSessionEnvelope`, `daemon/bootstrap.ts`) — this listener only reacts to the ones that\n // carry a `model`. Lives for the whole connection (not gated by `restoredRef`, unlike the\n // one-shot restore fetch above), so a model change on any session — active or not — is captured.\n useEffect(() => {\n if (status !== \"open\" || !client) return;\n return client.onAgentUpdate((msg) => {\n if (hasStringModel(msg)) {\n const modelProvider = typeof msg.modelProvider === \"string\" ? msg.modelProvider : undefined;\n useSessionStore.getState().setModelByAgentId(msg.agentId, msg.model, modelProvider);\n }\n });\n }, [status, client]);\n}\n","/**\n * Terminal restore — on a fresh `open` connection, fetches every terminal currently running on\n * the daemon (`list_terminals_request`) and reopens a tab for each one, mirroring\n * `use-session-restore.ts`'s one-shot restore pattern but for terminals instead of chat sessions.\n * A terminal can outlive its tab (a daemon restart losing the tab's in-memory link, a dev-mode\n * double-mount race losing the created slot before it's persisted onto the tab, or simply a\n * terminal created outside this UI — the CLI/MCP, or another connected client) and this is what\n * makes it reappear automatically as an ordinary closable tab instead of leaking silently or\n * needing a separate \"orphaned terminals\" management view. `TerminalPanel`'s slot-creation effect\n * only fires `create_terminal_request` when a tab's `data.slot` is still `null`, so opening a tab\n * with the terminal's already-known slot here just subscribes to its existing stream (reattach),\n * never spawns a second PTY.\n */\n\nimport { useEffect, useRef } from \"react\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { useTabStore, tabIds } from \"@pi-studio-ui/stores/tab-store.js\";\nimport { normalizeCwd } from \"@pi-studio-ui/features/sessions/workspace-grouping.js\";\nimport { resolveHome } from \"@pi-studio-ui/stores/explorer-store.js\";\n\ninterface TerminalRestoreEntry {\n slot: number;\n name: string;\n cwd?: string;\n closed: boolean;\n}\n\ninterface TerminalsListResponse {\n terminals?: TerminalRestoreEntry[];\n}\n\nexport function useTerminalRestore(): void {\n const client = useConnectionStore((s) => s.client);\n const status = useConnectionStore((s) => s.status);\n const restoredRef = useRef(false);\n\n useEffect(() => {\n if (status !== \"open\" || !client) return;\n if (restoredRef.current) return; // one restore per connection lifetime\n restoredRef.current = true;\n\n void (async () => {\n let terminals: TerminalRestoreEntry[] = [];\n try {\n const res = await client.connection.request<TerminalsListResponse>(\n \"list_terminals_request\",\n {},\n );\n terminals = res.terminals ?? [];\n } catch {\n terminals = [];\n }\n if (terminals.length === 0) return;\n\n const homeDir = await resolveHome(client).catch(() => null);\n const openTab = useTabStore.getState().open;\n for (const entry of terminals) {\n if (entry.closed) continue;\n const workspaceCwd = normalizeCwd(entry.cwd || \"~\", homeDir);\n openTab({\n id: tabIds.terminal(entry.slot),\n kind: \"terminal\",\n label: entry.name,\n closable: true,\n data: { slot: entry.slot, cwd: workspaceCwd },\n workspaceCwd,\n });\n }\n })();\n }, [status, client]);\n}\n","/**\n * Global keymap: Ctrl/Cmd+T new terminal, Ctrl/Cmd+W close active tab, Esc close overlays\n * (POC `document.addEventListener(\"keydown\", …)`, POC_TO_APP_PLAN_UI.md §4.8).\n */\n\nimport { useEffect } from \"react\";\nimport { useTabStore, openNewTerminal, closeTab } from \"@pi-studio-ui/stores/tab-store.js\";\nimport { useUiStore } from \"@pi-studio-ui/stores/ui-store.js\";\n\nexport function useShortcuts(): void {\n useEffect(() => {\n function onKeyDown(ev: KeyboardEvent) {\n const ui = useUiStore.getState();\n\n if (ev.key === \"Escape\") {\n ui.closeCwdPicker();\n ui.closeSessionMenu();\n return;\n }\n\n const mod = ev.ctrlKey || ev.metaKey;\n if (mod && ev.key.toLowerCase() === \"t\") {\n ev.preventDefault();\n // Captured synchronously here, not inside the terminal's mount effect — a rapid\n // workspace-switch-then-Ctrl+T must never race the async `create_terminal_request`.\n openNewTerminal(useTabStore.getState().activeWorkspaceCwd || \"~\");\n return;\n }\n\n if (mod && ev.key.toLowerCase() === \"w\") {\n const { activeTabId, tabs } = useTabStore.getState();\n if (!activeTabId) return;\n const tab = tabs.find((t) => t.id === activeTabId);\n if (tab?.closable !== false) {\n ev.preventDefault();\n closeTab(activeTabId);\n }\n }\n }\n\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, []);\n}\n","import { AppProviders } from \"./providers/AppProviders.js\";\nimport { WorkspacePage } from \"./routes/WorkspacePage.js\";\nimport { useConnectionBoot } from \"./hooks/use-connection.js\";\nimport { useSessionRestore } from \"./hooks/use-session-restore.js\";\nimport { useTerminalRestore } from \"./hooks/use-terminal-restore.js\";\nimport { useShortcuts } from \"./hooks/use-shortcuts.js\";\n\nfunction Boot() {\n useConnectionBoot();\n useSessionRestore();\n useTerminalRestore();\n useShortcuts();\n return null;\n}\n\n// Root component. Connection boot + session restore + global shortcuts run once at the top;\n// the workspace shell renders the 3-column layout.\nexport function App() {\n return (\n <AppProviders>\n <Boot />\n <WorkspacePage />\n </AppProviders>\n );\n}\n","import { StrictMode } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport \"./global.css\";\nimport { App } from \"./app.js\";\n\nconst root = document.getElementById(\"root\");\nif (!root) throw new Error(\"Missing #root element\");\n\ncreateRoot(root).render(\n <StrictMode>\n <App />\n </StrictMode>,\n);\n"],"file":"assets/index-BTP_UxAo.js"}