import type { GatewayWsUrlResult } from '@hermes/shared' import type { TranslucencyState } from '@hermes/shared/translucency' import type { PoolLimits } from '../electron/pool-limits' import type { WakeIndicatorState } from './lib/wake-indicator' import type { PetOverlayBounds, PetOverlayControl, PetOverlayOpenRequest, PetOverlayStatePayload } from './store/pet-overlay' import type { QuickEntryStatePush, QuickEntryStatus, QuickEntrySubmitPayload } from './store/quick-entry' export {} declare global { interface Window { hermesDesktop: { setUiLanguage?: (locale: string) => void // Resolve a backend connection. Omit `profile` (or pass the primary) for // the window's backend; pass a named profile to lazily spawn/reuse that // profile's backend from the pool. getConnection: (profile?: string | null) => Promise // Registry-scoped backend resolution: dial (connectionId, profile). An // empty/local connectionId delegates to the legacy getConnection path. getConnectionFor?: (payload: { connectionId?: null | string profile?: null | string }) => Promise // Registry-scoped fresh WS URL (same result contract as getGatewayWsUrl). getGatewayWsUrlFor?: (payload: { connectionId?: null | string profile?: null | string }) => Promise // Union agent roster across every registered connection. getAgentRoster?: () => Promise // Credential-free routes across the union connection registry. The // optional profile list is used only by the single-local v1 fallback; // endpoint and auth material never crosses the IPC boundary. getProfileRoutes: (profiles: string[]) => Promise // Reconnect-after-wake recovery: liveness-probe the cached PRIMARY backend // and drop it if a remote one has gone unreachable, so the next // getConnection() rebuilds a reachable descriptor instead of the renderer // re-dialing a dead remote forever. No-op for local backends (they // self-heal via the child 'exit' handler). `rebuilt` is true when a stale // remote cache was dropped. revalidateConnection: () => Promise<{ ok: boolean; rebuilt: boolean }> // Keepalive: mark a pool profile backend as recently used so the idle // reaper spares it while its chat is active. touchBackend: (profile?: string | null) => Promise<{ ok: boolean }> // Pool sizing (Settings → Advanced): device-local, live-applied by the // main process. get resolves the limits currently in force; set applies // (and persists) new ones, evicting/reaping to converge immediately. getPoolLimits: () => Promise setPoolLimits: (limits: { maxBackends?: number; idleMs?: number }) => Promise<{ ok: boolean limits: PoolLimits }> getGatewayWsUrl: (profile?: null | string) => Promise // Open (or focus) a standalone OS window for a single chat session so // the user can work with multiple chats side by side. Returns ok:false // with an error code when the sessionId is empty/invalid. `watch` opens // a spectator window (lazy resume — no agent build) for live-streaming // a running subagent's session. openSessionWindow: ( sessionId: string, opts?: { profile?: null | string; watch?: boolean } ) => Promise<{ ok: boolean; error?: string }> // Resume this session in the user's own terminal emulator (`hermes --tui // --resume `) — the external terminal, not the in-app pane. openSessionInTerminal: ( sessionId: string, opts?: { cwd?: string; profile?: string } ) => Promise<{ ok: boolean; error?: string }> // Open a new full-chrome app window — a peer instance of the primary that // renders the complete app against the shared backend, so the user can run // multiple GUI windows at once. openWindow: () => Promise<{ ok: boolean; error?: string }> // Pop the in-app Browser (webview + address bar) into its own OS window. // `tabId` is the `$previewTabs` id; closing the window fires // `onBrowserPopoutClosed` so the caller can dock the tab again. openBrowserWindow: (tabId: string) => Promise<{ ok: boolean; error?: string }> onBrowserPopoutClosed: (callback: (tabId: string) => void) => () => void // Claim a one-shot cross-window ambient cue (turn-end sound / spoken // reply). Resolves true for the first window to claim a key, false for // peers — so N open windows don't all fire the same cue. claimAmbientCue: (key: string) => Promise wakeIndicator?: { getState: () => Promise setState: (state: WakeIndicatorState) => void onState: (callback: (state: WakeIndicatorState) => void) => () => void } // The pop-out pet overlay: a transparent always-on-top window hosting only // the mascot. The main renderer drives it (open/close/drag + state push); // the overlay sends control messages back (pop-in, composer submit). petOverlay: { open: (request: PetOverlayOpenRequest) => Promise<{ ok: boolean; bounds?: PetOverlayBounds }> close: () => Promise<{ ok: boolean }> setBounds: (bounds: PetOverlayBounds) => void setIgnoreMouse: (ignore: boolean) => void setFocusable: (focusable: boolean) => void pushState: (payload: PetOverlayStatePayload) => void control: (payload: PetOverlayControl) => void onState: (callback: (payload: PetOverlayStatePayload) => void) => () => void onControl: (callback: (payload: PetOverlayControl) => void) => () => void } // HUD mode: the chrome-free floating chat. A FULL app renderer with its // own gateway (like an instance window), sized and skinned as a floating // bar — so it mounts the real composer rather than a lookalike. Main // owns the window; `onChanged` keeps every window's toggle truthful. hud?: { nativeDrag: boolean windowing?: { clientPlacement: boolean controlDrag: boolean nativeDrag: boolean solid: boolean workspaceTransfer: boolean } open: (request?: { sessionId?: null | string; profile?: null | string }) => Promise<{ ok: boolean }> close: () => Promise<{ ok: boolean }> setIgnoreMouse: (ignore: boolean) => void beginMove: () => void endMove: () => void moveBy: (delta: { width: number; height: number }) => void setWorkspaceTransfer?: (transferring: boolean) => void setBounds: (bounds: { x: number; y: number; width: number; height: number }) => void resetLayout: () => Promise<{ ok: boolean }> setFrost: (showing: boolean) => Promise<{ ok: boolean }> setSession: (sessionId: null | string) => void onGoto: (callback: (sessionId: string) => void) => () => void onChanged: (callback: (state: { open: boolean; sessionId: null | string }) => void) => () => void onCursor: (callback: (point: { x: number; y: number } | null) => void) => () => void onGameOverlay: (callback: (state: { active: boolean; app: string }) => void) => () => void } // Quick Entry: a global-hotkey mini composer window. Main owns the OS // shortcut registration + the persisted preference (it must restore the // shortcut on a cold launch without the renderer visiting Settings), so // the renderer reads/writes it here and adopts the authoritative reply. quickEntry: { getSettings: () => Promise // Returns the resulting state — including `registered: false` + // `error: 'taken'` when another app already owns the chord, so a failed // registration surfaces in Settings instead of failing silently. setSettings: (patch: { enabled?: boolean; shortcut?: string }) => Promise // Quick window → main: send this payload (main forwards it to the // primary renderer, which routes it to the target session and submits // through the normal prompt path) and hide. submit: (payload: QuickEntrySubmitPayload) => void // Quick window → main: hide without sending (Escape / blur). dismiss: () => void // Primary renderer → main → quick window: gateway connection state + // the recent-session options. Main caches the latest push and replays // it to a quick window spawned later. pushState: (payload: QuickEntryStatePush) => void // Quick window subscribes to those pushes. onState: (callback: (payload: QuickEntryStatePush) => void) => () => void // Primary renderer subscribes to submits captured by the quick window. onSubmit: (callback: (payload: QuickEntrySubmitPayload | string) => void) => () => void // Quick window subscribes to "you were just summoned" so it can reset // its draft and re-focus the input on every open. onShown: (callback: () => void) => () => void } getBootProgress: () => Promise getConnectionConfig: (profile?: null | string) => Promise saveConnectionConfig: (payload: DesktopConnectionConfigInput) => Promise applyConnectionConfig: (payload: DesktopConnectionConfigInput) => Promise testConnectionConfig: (payload: DesktopConnectionConfigInput) => Promise // Opt-in OS-keychain encryption for stored gateway secrets (default // off). `get` never touches the OS keychain; `set` re-encodes stored // secrets and can throw when the keychain is unusable. getSecretStorageEncryption: () => Promise<{ on: boolean }> setSecretStorageEncryption: (on: boolean) => Promise<{ on: boolean }> // v2 multi-connection registry: named agent sources, all persisted // together (local + any number of remote/cloud/ssh instances). connections: { list: () => Promise save: ( payload: DesktopRegistryConnectionInput ) => Promise<{ ok: boolean; connection: DesktopRegistryConnection; registry: DesktopConnectionsRegistry }> remove: (id: string) => Promise<{ ok: boolean; registry: DesktopConnectionsRegistry }> setPrimary: (id: string) => Promise<{ ok: boolean; registry: DesktopConnectionsRegistry }> setLaunchMode?: ( mode: 'last-used' | 'primary' ) => Promise<{ ok: boolean; registry: DesktopConnectionsRegistry }> setLastUsed?: (id: string) => Promise<{ ok: boolean; registry: DesktopConnectionsRegistry }> test: (id: string) => Promise // Drain/update/restore one Desktop-managed SSH install. External URL // and cloud sources are refused without touching their processes. updateManaged?: (id: string) => Promise // Fan out `hermes update` to every eligible registered connection; // cloud entries are skipped (platform-managed), each row independent. // excludeIds skips connections the caller updates through another // path (the everything-update flow's active backend + local client). updateAll?: (options?: { excludeIds?: string[] }) => Promise<{ ok: boolean; results: DesktopConnectionUpdateResult[] }> // Registry lifecycle push: fired when a connection is removed or // materially edited so the renderer can dispose (and re-dial) the // secondary gateways scoped to it. Optional: older Electron mains // don't emit it. onChanged?: ( callback: (payload: { connectionId: string; reason: 'removed' | 'saved' | 'updated' }) => void ) => () => void } sshConfigHosts: () => Promise sshResolveHost: (host: string) => Promise probeConnectionConfig: (remoteUrl: string) => Promise oauthLoginConnectionConfig: (remoteUrl: string) => Promise oauthLogoutConnectionConfig: (remoteUrl: string) => Promise // Hermes Cloud: one portal login powers discovery + silent per-agent // sign-in (cloud-auto-discovery Phase 3). cloud: { status: () => Promise login: () => Promise logout: () => Promise discover: (org?: string) => Promise agentSignIn: (dashboardUrl: string) => Promise } profile: { get: () => Promise // Persists the profile used on the next Desktop launch without // interrupting the live gateway workspace switch. remember: (name: string | null) => Promise // Persists the desktop's profile choice and relaunches the local // backend under the new HERMES_HOME (reloads the window). Pass null to // clear the preference. set: (name: string | null) => Promise } api: (request: HermesApiRequest) => Promise notify: (payload: HermesNotification) => Promise requestMicrophoneAccess: () => Promise /** read_window_below tool: metadata for the OS window directly underneath this one (never pixels). */ readWindowBelow?: () => Promise<{ frontmost: { app: string; title: string } | null note?: string platform: string window: { app: string bounds: { height: number; width: number; x: number; y: number } id: number title: string } | null } | null> readFileDataUrl: (filePath: string) => Promise /** Remote non-image attach: higher dedicated cap than preview/Settings default. */ readFileDataUrlForAttach?: (filePath: string) => Promise /** Settings → Chat: max size for local files loaded as data URLs (attach/preview). */ dataUrlReadMax?: { get: () => Promise<{ defaultMaxMb: number; maxBytes: number; maxMb: number }> set: (maxMb: number) => Promise<{ defaultMaxMb: number; maxBytes: number; maxMb: number }> } readFileText: (filePath: string) => Promise /** Full-source read for runtime desktop plugins (readFileText truncates * at the 512 KiB preview cap). Absent on older shells — callers fall * back to readFileText and must reject a `truncated` result. */ readPluginSource?: (filePath: string) => Promise selectPaths: (options?: HermesSelectPathsOptions) => Promise /** Native save dialog; returns the chosen path or null on cancel. */ selectSavePath?: (options?: { defaultPath?: string filters?: Array<{ extensions: string[]; name: string }> title?: string }) => Promise writeClipboard: (text: string) => Promise readClipboard: () => Promise saveGatewayFile?: (payload: { connectionId?: null | string path: string profile?: null | string suggestedName?: string }) => Promise<{ canceled?: boolean path?: string saved: boolean }> saveImageFromUrl: (url: string) => Promise /** Edit verb against the window's focused element (the custom context * menu's Cut/Copy/Paste/Select all). */ contextMenuEdit?: (command: 'copy' | 'cut' | 'paste' | 'selectAll') => Promise /** Copy the image under the LAST context-menu gesture (Chromium tracks * its coordinates on the main-process context-menu event). */ contextMenuCopyImage?: () => Promise /** Replace the misspelled word or add it to the dictionary. */ contextMenuSpellcheck?: (action: { kind: 'add' | 'replace'; word: string }) => Promise /** Add a word to the spell-check dictionary of a webview guest's * session (the tag exposes no session API). */ contextMenuGuestAddWord?: (payload: { webContentsId: number; word: string }) => Promise /** Spell-check facts for the gesture that opened the current menu; * fires shortly after the DOM contextmenu event. */ onContextMenuSpellcheck?: ( callback: (payload: { misspelledWord: string; suggestions: string[] }) => void ) => () => void saveImageBuffer: (data: ArrayBuffer | Uint8Array, ext: string, name?: string) => Promise /** Crop the in-app browser guest. `rect` is CSS pixels in the page viewport. */ capturePreview?: (payload: { rect?: { height: number; width: number; x: number; y: number } viewport?: { height: number; width: number } webContentsId: number }) => Promise saveClipboardImage: () => Promise getPathForFile: (file: File) => string normalizePreviewTarget: (target: string, baseDir?: string) => Promise watchPreviewFile: (url: string) => Promise /** Watch a directory for entry churn (disk-plugin door); same watcher * registry + onPreviewFileChanged channel as watchPreviewFile. Optional: * older Electron shells predate it and fall back to the readdir poll. */ watchDirectory?: (dir: string) => Promise stopPreviewFileWatch: (id: string) => Promise setActiveWork?: (payload: HermesActiveWork) => void setTitleBarTheme?: (payload: HermesTitleBarTheme) => void setNativeTheme?: (mode: 'dark' | 'light' | 'system') => void /** Main-process fact: this OS can back glass with a native material. */ glassSupported?: boolean /** Main-process fact: this OS can do any translucency at all (not Linux). */ translucencySupported?: boolean /** Launch flag: the app was started with --local, enabling the * local-models GUI surfaces. Absent/false = every local surface hides. */ localModelsEnabled?: boolean setTranslucency?: (payload: TranslucencyState) => void setKeepAwake?: (on: boolean) => void setDisableF12?: (blocked: boolean) => void setPreviewShortcutActive?: (active: boolean) => void openExternal: (url: string) => Promise /** One-click TurkServis sign-in (electron/aiturk-login.ts). API keys stay * in the main process; only the short code and a status come back. */ aiturkLogin?: { start: (scope?: { connectionId?: string; profile?: string }) => Promise poll: () => Promise cancel: () => Promise<{ ok: true }> overwriteCli: () => Promise<{ cli: AiturkCliStatus }> } /** "Telefon bağlantısı": opens the pairing window (electron/mobile-host.mjs). Windows only. */ aiturkMobile?: { open: () => Promise<{ ok: true }> } /** One-shot loopback callback listener for MCP OAuth against remote * backends (electron/mcp-oauth-callback-ipc.ts): bind on THIS machine, * pass redirectUri as client_redirect_uri to mcp.servers.oauth.start, * await the provider redirect, relay code/state via oauth.callback. */ mcpOauth?: { listen: () => Promise<{ id: string; redirectUri: string }> wait: ( id: string, timeoutMs?: number ) => Promise<{ code: null | string; error: null | string; state: null | string }> cancel: (id: string) => Promise } openPreviewInBrowser?: (url: string) => Promise fetchLinkTitle: (url: string) => Promise /** A site's icon as a data URL, or '' when it has none we can read. * Resolved and cached in the main process (electron/favicon.ts). */ resolveFavicon?: (url: string) => Promise sanitizeWorkspaceCwd: (cwd?: null | string) => Promise<{ cwd: string; sanitized: boolean }> settings: { getDefaultProjectDir: () => Promise<{ defaultLabel: string; dir: null | string; resolvedCwd: string }> pickDefaultProjectDir: () => Promise<{ canceled: boolean; dir: null | string }> setDefaultProjectDir: (dir: null | string) => Promise<{ dir: null | string }> } zoom?: { get: () => Promise<{ level: number; percent: number }> /** Synchronous zoom factor of this window (1 = 100%). */ factor?: () => number setPercent: (percent: number) => void onChanged: (callback: (payload: { level: number; percent: number }) => void) => () => void } revealLogs: () => Promise<{ ok: boolean; path: string; error?: string }> getRecentLogs: () => Promise<{ path: string; lines: string[] }> /** Persist a renderer error-boundary catch to desktop.log (fire-and-forget). */ reportRendererError?: (report: { label: string boundary: string message: string componentStack: string }) => void readDir: (path: string) => Promise gitRoot?: (path: string) => Promise // Reveal a path in the OS file manager (Finder / Explorer). revealPath?: (path: string) => Promise // Open a DIRECTORY (created if missing) in the OS file manager. openDir?: (path: string) => Promise<{ ok: boolean; error?: string }> // Local Desktop runtime-plugin root (/desktop-plugins), // resolved by Electron independently of the connected backend (#66899). // Created on demand; returns the normalized absolute path. desktopPluginsRoot?: () => Promise /** LOCAL `/logs` (profile-aware) — error card "Open Logs". */ logsRoot?: () => Promise // Local AGENT-plugin root (/plugins), same Electron-local // resolution. The disk door also scans it for `/desktop/plugin.js` // so one agent-plugin package can ship a desktop UI half. Optional: // older Electron shells predate it — the scanner then skips this root. agentPluginsRoot?: () => Promise // Rename a file/folder in place (new base name, same parent dir). renamePath?: (path: string, newName: string) => Promise<{ path: string }> // Write a small UTF-8 text file (hardened path, parent must exist). writeTextFile?: (path: string, content: string) => Promise<{ path: string }> // Move a file/folder to the OS trash (recoverable). trashPath?: (path: string) => Promise // Git-driven worktree management for the "Start work" flow. git?: { worktreeList: (repoPath: string) => Promise worktreeAdd: ( repoPath: string, options?: { name?: string; branch?: string; base?: string; existingBranch?: string } ) => Promise<{ path: string; branch: string; repoRoot: string }> worktreeRemove: ( repoPath: string, worktreePath: string, options?: { force?: boolean } ) => Promise<{ removed: string }> branchSwitch: (repoPath: string, branch: string) => Promise<{ branch: string }> // The local branches, plus the remote-tracking refs that have no local // branch, for the "convert a branch into a worktree" picker. branchList: (repoPath: string) => Promise // Local + remote-tracking branches for the "base branch" picker in the // new-worktree dialog. The remote default (origin/HEAD) is flagged so // the UI can preselect it. baseBranchList: (repoPath: string) => Promise // Compact working-tree status for the composer coding rail. Null on a // non-repo / remote backend (where the Electron probe can't run). repoStatus: (repoPath: string) => Promise // Working-tree-vs-HEAD unified diff for one file (the preview's diff // view). Empty string when the file is unchanged or not in a repo. fileDiff: (repoPath: string, filePath: string) => Promise // Codex-style review pane: changed files per scope, per-file diff, and // stage / unstage / revert. review: { list: (repoPath: string, scope: HermesReviewScope, baseRef?: null | string) => Promise diff: ( repoPath: string, filePath: string, scope: HermesReviewScope, baseRef?: null | string, staged?: boolean ) => Promise stage: (repoPath: string, filePath?: null | string) => Promise<{ ok: boolean }> unstage: (repoPath: string, filePath?: null | string) => Promise<{ ok: boolean }> revert: (repoPath: string, filePath?: null | string) => Promise<{ ok: boolean }> revParse: (repoPath: string, ref?: null | string) => Promise commit: (repoPath: string, message: string, push: boolean) => Promise<{ ok: boolean }> // Diff (staged-or-all) + recent commit subjects for drafting a // commit message. Reads only; empty strings off-repo. commitContext: (repoPath: string) => Promise<{ diff: string; recent: string }> push: (repoPath: string) => Promise<{ ok: boolean }> shipInfo: (repoPath: string) => Promise // The PR on each of the given branches — plus any known only by // number — for badging a list of sessions in one request instead of // one `pr view` per checkout. prList: (repoPath: string, branches: string[], numbers?: number[]) => Promise // A pasted PR review/issue comment URL resolved to its structured // context (author, body, file + line anchor, diff hunk). Null when // gh can't answer — the paste stays a plain URL. fetchPrComment: (repoPath: string, url: string) => Promise createPr: (repoPath: string) => Promise<{ url: string }> } // Repo-first discovery: scan bounded roots for git repos (depth-capped). scanRepos: ( roots: string[], options?: { maxDepth?: number; enabled?: boolean; excludePaths?: string[] } ) => Promise<{ root: string; label: string }[]> } terminal: { attach: (id: string) => Promise /** Best-effort current working directory of the live PTY child (POSIX * only; null on Windows or when unavailable). Used to reopen a tab * where the user last `cd`'d. */ cwd: (id: string) => Promise dispose: (id: string) => Promise onData: (id: string, callback: (payload: string) => void) => () => void onExit: (id: string, callback: (payload: HermesTerminalExit) => void) => () => void resize: (id: string, size: { cols: number; rows: number }) => Promise start: (options?: { cols?: number; cwd?: string; rows?: number }) => Promise write: (id: string, data: string) => Promise } reachPreviewUrl?: (url: string) => Promise setActiveConnectionRoute?: ( route: { connectionId?: null | string profile?: string registryScoped?: boolean } | null ) => void onClosePreviewRequested?: (callback: () => void) => () => void onPreviewNav?: (callback: (command: 'back' | 'forward' | 'reload') => void) => () => void onOpenFolderRequested?: (callback: () => void) => () => void onOpenUpdatesRequested?: (callback: () => void) => () => void onDeepLink?: ( callback: (payload: { kind: string; name: string; params: Record }) => void ) => () => void signalDeepLinkReady?: () => Promise<{ ok: boolean }> probePluginRepo?: (payload: { identifier?: string; repo?: string }) => Promise<{ ok: boolean agent: boolean desktop: boolean agentName?: string | null desktopName?: string | null warnings?: string[] insecure?: boolean error?: string }> installDesktopPlugin?: (payload: { identifier?: string repo?: string force?: boolean }) => Promise<{ ok: boolean; pluginName?: string; path?: string; error?: string }> onWindowStateChanged?: (callback: (payload: HermesWindowState) => void) => () => void onFocusSession?: (callback: (sessionId: string) => void) => () => void onNotificationAction?: (callback: (payload: { actionId: string; sessionId?: string }) => void) => () => void /** Plugin (and other session-less) notification body/action activation. */ onNotificationActivate?: ( callback: (payload: { actionId?: string; activate?: string; notifyId?: string; tag?: string }) => void ) => () => void onPreviewFileChanged: (callback: (payload: HermesPreviewFileChanged) => void) => () => void onBackendExit: (callback: (payload: BackendExit) => void) => () => void // Soft gateway-mode apply: primary backend was torn down without a window // reload. Wipe session lists (skeletons) and re-dial. onConnectionApplied?: (callback: () => void) => () => void onPowerResume?: (callback: () => void) => () => void getOnBattery?: () => Promise onBatteryChanged?: (callback: (onBattery: boolean) => void) => () => void onBootProgress: (callback: (payload: DesktopBootProgress) => void) => () => void getBootstrapState: () => Promise continueBootstrapLocal: () => Promise<{ ok: boolean }> recycleBackend?: (profile?: null | string) => Promise<{ ok: boolean }> resetBootstrap: () => Promise<{ ok: boolean }> repairBootstrap: () => Promise<{ ok: boolean }> cancelBootstrap: () => Promise<{ ok: boolean; cancelled: boolean }> onBootstrapEvent: (callback: (payload: DesktopBootstrapEvent) => void) => () => void getVersion: () => Promise /** Restart the app in place — loads the swapped bundle when bundleSwapPending. */ relaunchApp?: () => Promise getRemoteDisplayReason?: () => Promise updates: { check: () => Promise apply: (opts?: DesktopUpdateApplyOptions) => Promise getBranch: () => Promise<{ branch: string }> setBranch: (name: string) => Promise<{ branch: string }> onProgress: (callback: (payload: DesktopUpdateProgress) => void) => () => void } uninstall: { summary: () => Promise run: (mode: DesktopUninstallMode) => Promise } themes: { // Download a VS Code Marketplace extension and return the raw color // theme files it contributes. The renderer converts + persists them. fetchMarketplace: (id: string) => Promise // Search the Marketplace for color-theme extensions. An empty query // returns the most-installed themes. searchMarketplace: (query: string) => Promise } // Find-in-page: delegates to Electron's webContents.findInPage on the // IPC sender's window so Cmd+F from a secondary session window // searches that window (not the primary). `onFoundInPage` returns the // unsubscribe fn; the renderer wires it via `initFindInPageListener` // in store/find-in-page.ts and tears it down when the FindBar unmounts. findInPage: (query: string, options?: { forward?: boolean; findNext?: boolean }) => Promise<{ count: number }> stopFindInPage: () => Promise onFoundInPage: (callback: (result: { activeMatchOrdinal: number; count: number }) => void) => () => void // Main-process `before-input-event` forwards Ctrl/Cmd+F here so the // renderer can still open the FindBar when the OS compositor has // already grabbed the chord (#81727, e.g. Pop!_OS / GNOME). onOpenFindBarRequested: (callback: () => void) => () => void } } } export interface DesktopMarketplaceSearchItem { extensionId: string displayName: string publisher: string description: string installs: number } export interface DesktopMarketplaceThemeFile { label: string /** VS Code's `uiTheme` for this entry (vs-dark / vs / hc-black). */ uiTheme?: string /** Raw theme JSON (JSONC) text, parsed + converted by the renderer. */ contents: string } export interface DesktopMarketplaceThemeResult { extensionId: string displayName: string themes: DesktopMarketplaceThemeFile[] } export interface HermesTerminalSession { cwd: string id: string shell: string } export interface HermesTerminalExit { code: number | null signal: string | null } export interface DesktopVersionInfo { appVersion: string electronVersion: string nodeVersion: string platform: string hermesRoot: string /** True when the running renderer bundle predates desktop changes in the * installed source tree (runtime updated, app binary not rebuilt/swapped). */ bundleOutOfSync?: boolean /** Commits under apps/desktop/ the running bundle is missing (null unknown). */ bundleCommitsBehind?: null | number /** True when the bundle on disk is newer than the running process — a plain * app restart (no rebuild, no installer) is enough to load it. */ bundleSwapPending?: boolean } export type DesktopUninstallMode = 'full' | 'gui' | 'lite' export interface DesktopUninstallSummary { hermes_home: string agent_installed: boolean gui_installed: boolean source_built_artifacts: string[] packaged_app_paths: string[] userdata_dir: string userdata_exists: boolean platform: string running_app_path?: null | string probe?: string } export interface DesktopUninstallResult { ok: boolean mode?: DesktopUninstallMode willRemoveAppBundle?: boolean scriptPath?: string error?: string message?: string } export interface DesktopUpdateCommit { sha: string summary: string author: string at: number } export interface DesktopUpdateStatus { supported: boolean updateAvailable?: boolean branch?: string currentBranch?: string reason?: string message?: string error?: string /** Exact commits behind. null = update available, but the count is * unknowable (shallow clone without a merge-base) — never render it as a * literal number. */ behind?: number | null currentSha?: string /** Backend only: the version string the backend reports for itself. */ currentVersion?: string targetSha?: string commits?: DesktopUpdateCommit[] dirty?: boolean fetchedAt?: number } export type DesktopUpdateDirtyStrategy = 'abort' | 'stash' | 'force' export interface DesktopUpdateBlocker { pid: number name: string cmdline: string kind: 'local-preview' | 'other' safeToStop: boolean label?: string port?: number createTime?: number } export interface DesktopUpdateApplyOptions { dirtyStrategy?: DesktopUpdateDirtyStrategy /** User confirmed that Desktop may stop freshly re-scanned safe local preview servers. */ stopSafeBlockers?: boolean } export interface DesktopUpdateApplyResult { ok: boolean branch?: string error?: string message?: string blockers?: DesktopUpdateBlocker[] /** True when no staged updater exists (CLI install) and the user should run * `hermes update` themselves. `command` is the exact line to run. */ manual?: boolean command?: string hermesRoot?: string /** True when the backend was updated but the GUI couldn't be relaunched in * place (AppImage / dev run): the new version loads on next launch. */ backendUpdated?: boolean /** False when the running GUI package was NOT replaced by this update * (Linux GUI/backend skew, or a sandbox-blocked relaunch). Distinguishes * "backend only" outcomes from a real in-place GUI relaunch. (#45205) */ guiUpdated?: boolean /** True for the Linux GUI/backend-skew terminal state: backend updated but * the running AppImage/.deb/.rpm shell is unchanged and must be * reinstalled. Renders a closeable "update the desktop app" message. */ guiSkew?: boolean /** True when the update finished but the app must be quit + reopened by hand * (e.g. the rebuilt sandbox helper isn't launchable): keep a working * window, don't auto-quit into a dead app. (#45205) */ manualRestart?: boolean /** True when the auto-relaunch was skipped specifically because the rebuilt * chrome-sandbox helper is not launchable (not root:root + setuid). */ sandboxBlocked?: boolean /** True when a detached relauncher took over (macOS bundle swap / Linux * re-exec): the app is about to quit and reopen itself. */ handedOff?: boolean } export type DesktopUpdateStage = | 'idle' | 'prepare' | 'fetch' | 'pull' | 'pydeps' | 'update' | 'rebuild' | 'restart' | 'done' | 'manual' /** Backend updated but the running GUI package (AppImage/.deb/.rpm) was NOT * changed — the user must update/reinstall the desktop app. Terminal, * closeable; never claims the GUI was updated. (#45205) */ | 'guiSkew' | 'error' export interface DesktopUpdateProgress { stage: DesktopUpdateStage message: string percent: number | null error: string | null at: number } export interface DesktopPluginProfileRoute { // Registry source identity. Pair with profile; profile names are not unique // across sources. connectionId: string mode: 'local' | 'remote' profile: string targetProfile: string } export interface HermesConnection { baseUrl: string darwinMajor?: number isFullscreen: boolean // The live, RESOLVED connection mode. Only ever 'local' or 'remote' — a // 'cloud' saved-config entry resolves to a 'remote' connection under the hood // (cloud-auto-discovery Q3/Q6), so this never carries 'cloud'. mode?: 'local' | 'remote' authMode?: 'oauth' | 'token' remoteHost?: string remoteIdentity?: string remoteKind?: 'cloud' | 'ssh' | 'url' remoteHermesVersion?: string nativeOverlayWidth: number source?: 'env' | 'local' | 'settings' token: string wsUrl: string logs: string[] // Set for pool (non-primary) backends so the renderer knows which profile a // connection belongs to. profile?: string // The registry connection this descriptor resolves to. Registry-scoped // secondaries carry it directly; legacy primary remotes preserve it from // their selected stored route before dialing. connectionId?: string // True only when getConnectionFor explicitly resolved a v2 registry route. // An inferred connectionId identifies the visible source but its v1 profile // name may still be a client-side routing alias rather than a backend profile. registryScoped?: boolean // True only when `profile` is a request scope on the shared primary backend. // A pooled backend also carries `profile`, so presence alone cannot identify // the shared-primary routing case. sharedPrimary?: boolean // True when `profile` is a request scope on a SHARED registry remote/cloud // backend (one host, many profiles) — the registry analogue of sharedPrimary. sharedRemote?: boolean windowButtonPosition: { x: number; y: number } | null } export interface HermesTitleBarTheme { background: string foreground: string } /** Turns in flight, so the main process can confirm before a quit kills them. */ export interface HermesActiveWork { count: number titles: string[] } export interface HermesWindowState { darwinMajor?: number isFullscreen: boolean isMinimized?: boolean isVisible?: boolean nativeOverlayWidth: number windowButtonPosition: { x: number; y: number } | null } export interface DesktopActiveProfile { // The desktop's stored profile preference, or null when unset (legacy launch // that defers to the sticky active_profile / default). profile: string | null } export interface DesktopConnectionConfig { envOverride: boolean // The saved connection mode. 'cloud' is a Hermes Cloud connection: it carries // a remote-shaped block (remoteUrl = the selected agent's dashboardUrl, // remoteAuthMode 'oauth') but is remembered as cloud so settings reopens into // the cloud picker. Resolution treats cloud exactly as remote // (cloud-auto-discovery Q3/Q6). mode: 'local' | 'remote' | 'cloud' | 'ssh' // The profile this config describes, or null for the global/default // connection. Per-profile entries let a profile point at its own backend. profile: null | string remoteAuthMode: 'oauth' | 'token' remoteOauthConnected: boolean remoteTokenPreview: string | null remoteTokenSet: boolean // Whether OS-keychain-backed encryption (Electron safeStorage) is currently // available on this machine. When false, a persisted remote token can only be // stored as plain text on disk (with an explicit opt-in). secureTokenStorage: boolean // Whether the currently-persisted remote token is stored with encoding // 'plain' (i.e. plain text on disk in connection.json), which happens when // the user opted in on a machine without secure storage. remoteTokenPlainText: boolean remoteUrl: string // For a 'cloud' connection: the persisted Hermes Cloud org (slug or id) the // connected instance was discovered under, so Settings → Gateway can reopen // into that org. Empty string for remote/local. cloudOrg: string sshHost: string sshUser: string sshPort: number | null sshKeyPath: string sshRemoteHermesPath: string sshRemoteProfile: string } export interface DesktopConnectionConfigInput { mode: 'local' | 'remote' | 'cloud' | 'ssh' // When set, the save/apply/test targets this profile's per-profile remote // override instead of the global connection. profile?: null | string remoteAuthMode?: 'oauth' | 'token' remoteToken?: string // When true and secure (OS-keychain) storage is unavailable, persist the // remote token as plain text on disk instead of failing. Requires an explicit // user opt-in from the renderer. allowPlainTextToken?: boolean remoteUrl?: string // For a 'cloud' connection: the selected Hermes Cloud org (slug or id) to // persist so Settings can reopen into it. Ignored for remote/local modes. cloudOrg?: string sshHost?: string sshUser?: string sshPort?: number | null sshKeyPath?: string sshRemoteHermesPath?: string sshRemoteProfile?: string } export interface DesktopConnectionTestResult { baseUrl?: string ok?: boolean version?: string | null reachable?: boolean sshError?: | 'auth-failed' | 'hermes-not-found' | 'host-key-changed' | 'timeout' | 'unreachable' | 'unsupported-platform' | 'update-required' | 'unknown' | null error?: string | null host?: string remoteHermesPath?: string remoteHermesVersion?: string remotePlatform?: string } // ── v2 multi-connection registry (named agent sources) ───────────────────── export type DesktopConnectionKind = 'cloud' | 'local' | 'remote' | 'ssh' // A registered agent source as the renderer sees it: token bytes never cross // the IPC boundary (preview + set flag instead, like DesktopConnectionConfig). export interface DesktopRegistryConnection { id: string kind: DesktopConnectionKind // Required, registry-unique device name ("Homelab", "Work laptop"). label: string url?: string authMode?: 'oauth' | 'token' org?: string host?: string user?: string port?: number keyPath?: string remoteHermesPath?: string remoteProfile?: string tokenSet: boolean tokenPreview: null | string // Names of the stored extra gateway headers (Cloudflare Access etc.); // header VALUES are secrets and never cross the IPC boundary. Optional so // fixtures/older payloads without the field remain valid. headerNames?: string[] // Last-known stable backend identity (the /api/status `install_id`). // Present once a roster enumeration or connection test has seen it; two // connections sharing it are one physical backend registered under two // addresses (display-only "Same backend as …" hint in Settings). installId?: string } export interface DesktopConnectionsRegistry { version: number // id of the connection that owns the window/primary backend. primary: string // Preserve old installs by defaulting to the explicit primary; users may // instead resume the last successfully opened source. launchMode?: 'last-used' | 'primary' // Last source the Sessions workspace opened successfully. Optional for // compatibility with an older Electron main during a rolling app update. lastUsed?: string // Whether OS-keychain-backed encryption (Electron safeStorage) is available; // false drives the plain-text token opt-in on keyring-less Linux. secureTokenStorage: boolean connections: DesktopRegistryConnection[] } export interface DesktopRegistryConnectionInput { // Present for edits; omitted on create (the main process mints the id). id?: string kind: DesktopConnectionKind label: string url?: string authMode?: 'oauth' | 'token' // Plaintext token to store (encrypted at rest); omit to keep the saved one. token?: string allowPlainTextToken?: boolean // Extra gateway headers for remote/cloud entries (access proxies such as // Cloudflare Access). The map is authoritative when present: name → new // plaintext value (encrypted at rest), or null to keep the stored secret // for that name. Omit the field entirely to keep the saved set unchanged. headers?: Record org?: string host?: string user?: string port?: null | number keyPath?: string remoteHermesPath?: string remoteProfile?: string } // One agent in the union roster: a profile on a registered source, with the // pre-computed @name-device handle for duplicate profile names. export interface DesktopRosterAgent { connectionId: string connectionKind: DesktopConnectionKind connectionLabel: string profile: string targetProfile?: string handle: string } export interface DesktopAgentRoster { agents: DesktopRosterAgent[] sources: { connectionId: string label: string kind: DesktopConnectionKind reachable: boolean error?: string // Stable backend identity (/api/status install_id) when known. installId?: string }[] } // Per-connection result row from the update fan-out. export interface DesktopConnectionUpdateResult { connectionId: string label: string kind: DesktopConnectionKind ok: boolean skipped?: boolean reason?: string detail?: string error?: string } export type DesktopManagedConnectionUpdateOutcome = 'updated' | 'update-failed' | 'restore-failed' | 'update-and-restore-failed' | 'refused' export interface DesktopManagedUpdateReceipt { correlationId: string // Additive receipt outcomes remain forward-compatible with newer updater // kernels instead of making an older renderer reject their proof. outcome: string startedAt?: string finishedAt?: string preSha?: string postSha?: string preVersion?: string postVersion?: string stopReason?: string } export interface DesktopManagedConnectionUpdateResult { connectionId: string correlationId: string ok: boolean updateOk: boolean restoreOk: boolean outcome: DesktopManagedConnectionUpdateOutcome exitCode: number | null receipt: DesktopManagedUpdateReceipt | null scopes: Array<{ profile: string; restored: boolean; error?: string }> error?: string message?: string } export interface DesktopSshResolveResult { hostname: string | null identityFile: string | null port: number | null user: string | null } export interface DesktopSshHostsResult { hosts: string[] } export interface DesktopAuthProvider { name: string displayName: string // True when this provider authenticates with a username + password // (the gateway's /login page renders a credential form) rather than an // OAuth redirect. The session/cookie/ws-ticket machinery is identical; // only the login-page form and the desktop's button copy differ. supportsPassword?: boolean } export interface DesktopConnectionProbeResult { baseUrl: string reachable: boolean authMode: 'oauth' | 'token' | 'unknown' providers: DesktopAuthProvider[] version: string | null error: string | null } export interface DesktopOauthLoginResult { ok: boolean baseUrl: string connected: boolean } export interface DesktopOauthLogoutResult { ok: boolean connected: boolean } // --- Hermes Cloud (cloud-auto-discovery Phase 3) --- export interface DesktopCloudStatus { // The portal base URL the desktop talks to (default or env-overridden). portalBaseUrl: string // Whether the OAuth partition holds a live Nous portal (Privy) session — the // portal authenticates via Privy, so this reflects the privy-token cookie, NOT // the hermes gateway session cookies. See cookiesHavePrivySession. signedIn: boolean } // A discovered Hermes Cloud agent — the trimmed DTO from NAS GET /api/agents. export interface DesktopCloudAgent { id: string name: string status: string // null until the agent has a provisioned dashboard (show "provisioning…"). dashboardUrl: string | null // "active" | "degraded" | "down" | "unknown". dashboardGatewayState: string } // An org the signed-in user belongs to — for the org picker shown when a // multi-org user's discovery call needs disambiguation (NAS 409). export interface DesktopCloudOrg { id: string slug: string | null name: string isPersonal: boolean // "OWNER" | "MEMBER". role: string } // Discovery result: either the agent list, OR a request to pick an org first // (multi-org user, no org chosen yet). The renderer shows a picker on the // latter and re-calls discover(org). On the agents branch, `org` echoes the // authoritatively-resolved org the list was scoped to (from NAS), so the // desktop persists it without relying on transient picker state. export type DesktopCloudDiscoverResult = | { agents: DesktopCloudAgent[]; org?: DesktopCloudOrg | null; needsOrgSelection?: false } | { needsOrgSelection: true; orgs: DesktopCloudOrg[] } export interface DesktopCloudAgentSignInResult { // The agent gateway base URL the silent sign-in targeted. baseUrl: string // Whether the agent's gateway session cookie landed (silent cascade done). connected: boolean } export interface DesktopBootProgress { error: string | null fakeMode: boolean /** True when the boot failure is a Nous Cloud agent that is down (HTTP 502/503/504). */ isCloudBackendDown?: boolean message: string phase: string progress: number /** * True when the boot failure carried by `error` was a TRANSIENT remote * failure (dropped SSH/HTTP registered connection, mint timeout) that the * renderer may retry automatically. Absent/false on success updates, * local failures, and confirmed reauth rejections. */ retryable?: boolean running: boolean /** Structured HTTP status when the boot failure carried one (e.g. 503). */ statusCode?: number | null timestamp: number } // First-launch install ("bootstrap") event types -- emitted by // electron/bootstrap-runner.ts and observed by the renderer install overlay. // Mirrors the event shapes emitted by runBootstrap()'s onEvent callback. export interface DesktopBootstrapStageDescriptor { name: string title?: string category?: string needs_user_input?: boolean } export type DesktopBootstrapStageState = 'pending' | 'running' | 'succeeded' | 'skipped' | 'failed' export interface DesktopBootstrapStageResult { state: DesktopBootstrapStageState durationMs: number | null startedAt: number | null json: { ok: boolean; skipped?: boolean; reason?: string | null; stage: string } | null error: string | null } export interface DesktopBootstrapUnsupportedPlatform { platform: string activeRoot: string installCommand: string docsUrl: string } export interface DesktopBootstrapSetupChoice { platform: string activeRoot: string } export interface DesktopBootstrapState { active: boolean manifest: { type: 'manifest'; stages: DesktopBootstrapStageDescriptor[]; protocolVersion: number | null } | null stages: Record error: string | null log: Array<{ ts: number; stage: string | null; line: string; stream?: 'stdout' | 'stderr' }> startedAt: number | null completedAt: number | null setupChoice: DesktopBootstrapSetupChoice | null unsupportedPlatform: DesktopBootstrapUnsupportedPlatform | null } export type DesktopBootstrapEvent = | { type: 'dismissed' } | { type: 'setup-choice' active: boolean platform?: string activeRoot?: string } | { type: 'manifest'; stages: DesktopBootstrapStageDescriptor[]; protocolVersion: number | null } | { type: 'stage' name: string state: DesktopBootstrapStageState durationMs?: number json?: DesktopBootstrapStageResult['json'] error?: string | null } | { type: 'log'; stage?: string | null; line: string; stream?: 'stdout' | 'stderr' } | { type: 'complete'; marker: Record } | { type: 'failed'; stage?: string | null; error: string } | { type: 'unsupported-platform' platform: string activeRoot: string installCommand: string docsUrl: string } export type AiturkCliStatus = 'ok' | 'missing' | 'outdated' | 'other-account' | 'unknown-account' | 'failed' export interface AiturkLoginStart { userCode: string verificationUri: string expiresAt: number } export type AiturkLoginPoll = | { state: 'pending' } | { state: 'expired' } | { state: 'connected'; cli: AiturkCliStatus } | { state: 'error'; error: string } export interface HermesApiRequest { path: string method?: string body?: unknown // Single-file multipart upload (FastAPI UploadFile endpoints). Mutually // exclusive with `body`; bytes transfer over IPC as a structured-clone // ArrayBuffer. Token-mode backends only. upload?: { filename: string; contentType?: string; bytes: ArrayBuffer } timeoutMs?: number // Route this REST call to a specific profile's backend. Omit for the primary // (window) backend. Read-only cross-profile data is served by the primary, so // this is only needed for profile-scoped live/settings calls. profile?: string | null // Route this REST call to a specific REGISTERED gateway connection (v2 // registry). Data owned by a remote gateway — cron jobs and their run // sessions — lives in that host's state.db, so requests for it must resolve // through the owning connection, not the local profile pool. Omit / '' to // keep the legacy profile-routed path; explicit 'local' forces this device. connectionId?: string | null } export interface HermesNotification { title?: string body?: string silent?: boolean kind?: string sessionId?: string /** Dedupe discriminator for session-less notifications (e.g. plugin id). */ tag?: string /** Absolute icon path for Electron `Notification`. */ icon?: string /** Resolved hash-router path opened on body click (plugin / deeplink-compatible). */ activate?: string /** Renderer handle for onActivate / onAction callbacks. */ notifyId?: string actions?: { id: string; text: string; activate?: string }[] } export interface HermesPreviewTarget { binary?: boolean byteSize?: number kind: 'file' | 'url' label: string large?: boolean language?: string mimeType?: string path?: string previewKind?: 'binary' | 'html' | 'image' | 'pdf' | 'text' renderMode?: 'preview' | 'source' source: string url: string } export interface HermesReadFileTextResult { binary?: boolean byteSize?: number language?: string mimeType?: string path: string text: string truncated?: boolean } export interface HermesPreviewWatch { id: string path: string } // A real git worktree as reported by `git worktree list` (source of truth for // the "Start work" flow), as opposed to the session-cwd-derived grouping above. export interface HermesGitWorktree { path: string branch: null | string isMain: boolean detached: boolean locked: boolean } // A branch that the "convert a branch into a worktree" picker offers: the local // heads, plus the remote-tracking refs that have no local branch yet. // `checkedOut` means that a selection opens that checkout. `isDefault` means // that a selection switches the main checkout, and does not make // `.worktrees/main`. `isRemote` means that a selection first makes a local // branch that tracks the remote one. export interface HermesGitBranch { name: string checkedOut: boolean isDefault: boolean isRemote: boolean worktreePath: null | string } // A branch the new worktree can be based on: local heads + remote-tracking // refs. `isRemote` distinguishes `origin/main` from a local `main` (the UI // may show a remote glyph); `isDefault` flags origin/HEAD so the dialog can // preselect it. export interface HermesGitBaseBranch { name: string isRemote: boolean isDefault: boolean } // A single changed path from `git status --porcelain=v2`, classified by state // so the coding rail / switcher can group + open the right diff. export interface HermesRepoStatusFile { path: string staged: boolean unstaged: boolean untracked: boolean conflicted: boolean } // Compact working-tree status for the composer coding rail (parsed from // `git status --porcelain=v2 --branch`). export interface HermesRepoStatus { branch: null | string // The repo's trunk ("main" / "master" / …), so the UI can offer "branch off // the default" from anywhere. Null when no trunk is detected. defaultBranch: null | string detached: boolean ahead: number behind: number staged: number unstaged: number untracked: number conflicted: number // Total distinct changed paths (tracked modified + conflicts + untracked). changed: number // +/- line counts of tracked changes vs HEAD (staged + unstaged). Untracked // files aren't in the diff, so they don't contribute lines. added: number removed: number // Capped changed-file list (REPO_STATUS_FILE_CAP) for the diff/open actions. files: HermesRepoStatusFile[] } // Diff scope for the review pane, mirroring Codex: uncommitted working-tree // changes, all changes vs the branch base, or everything since the current // turn began. export type HermesReviewScope = 'branch' | 'lastTurn' | 'uncommitted' // One changed file in the review pane (status letter, +/- lines, staged flag). export interface HermesReviewFile { path: string added: number removed: number // M(odified) A(dded) D(eleted) R(enamed) C(opied) U(nmerged) ?(untracked) status: string staged: boolean } export interface HermesReviewList { files: HermesReviewFile[] // The resolved base ref the scope diffed against (branch merge-base / turn // baseline), or null for the uncommitted scope. base: null | string } // The branch's PR (if any) as reported by `gh pr view`. export interface HermesReviewPr { url: string state: string number: number } // One repo's PRs as reported by `gh pr list`, each tied to the branch it was // opened from — how a session row finds its own PR. export interface HermesBranchPullRequest { branch: string draft: boolean number: number /** `open` | `closed` | `merged`, lowercased from gh. */ state: string title: string url: string } export interface HermesRepoPullRequests { ghReady: boolean prs: HermesBranchPullRequest[] } // A PR review/issue comment resolved from a pasted GitHub URL — the composer's // review-comment attachment context. `path`/`line`/`diffHunk` are empty for // conversation-tab (issue) comments; `line` is null when the comment is // outdated and only `original_line` remained. export interface HermesPrComment { author: string body: string diffHunk: string kind: 'issue' | 'review' line: null | number path: string prNumber: number startLine: null | number url: string } // gh availability/auth + the current branch's PR — drives the review pane's PR // button (disabled when gh isn't ready, "Open PR" vs "Create PR" otherwise). export interface HermesReviewShipInfo { ghReady: boolean pr: HermesReviewPr | null } export interface HermesReadDirEntry { name: string path: string isDirectory: boolean } export interface HermesReadDirResult { entries: HermesReadDirEntry[] error?: string } export interface HermesPreviewFileChanged { id: string path: string url: string } export interface HermesSelectPathsOptions { title?: string defaultPath?: string directories?: boolean multiple?: boolean /** Backend profile that produced defaultPath; Electron uses it for WSL gating. */ profile?: string filters?: Array<{ name: string; extensions: string[] }> } export interface BackendExit { code: number | null signal: string | null }