Desktop Runtime — Electron, the terminal "TUI", deep links
The desktop app (packages/desktop) is a hardened Electron shell around the same
web editor. The owner asked specifically about an "integrated browser" — the
honest finding is there is no embedded browser pane. What the README calls a
"built-in TUI in the Desktop app" is a real node-pty terminal, and the
agent-facing surfaces are the web preview plus the terminal where a harness CLI
can run. This doc maps what's actually there.
1. Electron security posture
The main window is locked down (packages/desktop/src/main/index.ts:279-282):
webPreferences: { contextIsolation: true, nodeIntegration: false, sandbox: true }All main↔renderer traffic goes through compile-time-typed IPC: a single
createInvoker() factory constrains channel names and argument types to a
RequestChannels contract (packages/desktop/src/shared/ipc-invoke.ts,
ipc-channels.ts). A custom Biome lint rule, no-loosely-typed-webcontents-ipc,
fails the build on any raw ipcRenderer.invoke() call
(packages/desktop/tests/integration/no-loosely-typed-webcontents-ipc.test.ts:18),
so loosely-typed IPC can't sneak back in. The renderer is a thin shell that loads
the React bundle from packaged extra-resources
(packages/desktop/src/renderer/index.html:11).
On the "integrated browser": searches for
BrowserView/WebContentsView/<webview>come up empty. The only browser-adjacent code isembed-referer.ts:3-8, which rewrites the HTTPRefererheader for embedded YouTube iframes inside the editor. The "browser" surface agents use is OpenKnowledge's own web preview (the:39847UI), which agents open via the "Open with AI" handoff — not a separate Chromium pane.
2. The terminal — the real "TUI"
OK runs a full PTY-backed terminal inside the desktop app, split across two processes for isolation:
-
PTY host — a forked
utilityProcess(utilityProcess.fork('utility/pty-host.js'), nearmain/index.ts:1780) that spawns login shells withnode-pty:// packages/desktop/src/utility/pty-host.ts:178-185 pty = deps.spawn(shell, ['-l', '-i'], { name: 'xterm-256color', cols, rows, … }); -
Terminal manager (
main/terminal-manager.ts) — multiplexes sessions per window with backpressure (high/low water marks, pause/resume) so a runaway process can't flood the renderer. -
Renderer bridge (
preload/index.ts:498-525) —terminal.create/input/resize/ onData/onExit, plus the agent-specificterminal.claudePreflight()(:522) andterminal.rewireClaudeMcp()(:524).
This is how a coding-agent CLI runs inside OpenKnowledge: it's a normal login
shell, so you can launch claude, codex, or opencode in it. claudePreflight
checks whether Claude is ready in the project and rewireClaudeMcp re-runs the MCP
wiring on demand (main/index.ts ok:terminal:claude-assist handler near :2310,
mcp-wiring.ts:1-30). So the terminal isn't just a convenience — it's a host for
the agent that OK then talks to over MCP.
3. Desktop ↔ harness handoff
- Cursor: resolves the Cursor binary (
which cursor/ protocol lookup) and spawns it on a file path (main/ipc-handlers.ts:147-181,resolveCursorSpawnInvocation), then fires thecursor://deep-link. - Deep links inbound: a custom
openknowledge://scheme and universalhttps://openknowledge.ai/d/...links resolve to owner/repo/branch/target so a share URL opens the right doc (main/url-scheme.ts:83-93). - Claude Desktop skill:
bridge.skill.buildAndOpenbuilds theclaude-coworkskill ZIP and drops it in Downloads (main/ipc/install-skill.ts:32,68,105). - Handoff telemetry: every dispatch is a
HandoffStatsLineoverclaude-cowork/claude-code/codex/cursorwith outcome and host (shared/ipc-channels.ts:109-124).
4. Secrets — native keyring
Credentials (the GitHub OAuth token, embedding API keys) live in the OS
keychain via @napi-rs/keyring — macOS Keychain, Windows Credential Manager,
Linux Secret Service (packages/desktop/src/utility/keyring-smoke.ts). Keyring
access runs in the utility process, reached over correlation-ID'd debug IPC
(main/debug-ipc.ts), never from the renderer. A packaging check
(scripts/verify-keyring-in-packaged-dmg.mjs) verifies the native binding
survives into the signed DMG. No homegrown crypto, no plaintext config secrets.
What's worth stealing (for a desktop meta-harness)
- Typed IPC enforced by lint. One
createInvokercontract + a build-failing rule means every main↔renderer channel is documented and refactor-safe — exactly what you want when the desktop app is the trust boundary hosting multiple agents. - PTY in a forked utility process with backpressure. Isolate each agent CLI's terminal so a flood or crash can't take down the renderer; pause/resume keeps memory bounded.
- A login-shell terminal is the universal agent host. Rather than embed each
agent, give it a real terminal and talk to it over MCP.
claudePreflight/rewireClaudeMcpshow how to make that host self-healing. - Native keyring, accessed only from privileged processes. Don't roll your own secret store; reach it over typed IPC, never from the renderer.
- Inbound deep links + outbound spawn give you a two-way bridge to external IDEs without copy-paste.