v0.7.0
Part of the OpenKnowledge changelog.
Minor Changes
feat(desktop): decouple the Open Knowledge server's lifecycle from the Electron app
Packaged desktop builds now spawn the OK server as a fully-detached
child_process.spawnof the bundleddist/cli.mjs startunderELECTRON_RUN_AS_NODE=1. The server runs in its own process group (detached: true,stdio: 'ignore',.unref()) and survives Electron parent exit — closing the editor window OR quitting the desktop no longer affects the server. MCP-connected agents (Claude Code, Cursor, Codex) keep working through both events.Server lifecycle. The desktop becomes one client of the project server, not its owner. Every desktop window is effectively in attach mode — the production detached-spawn path bootstraps the server then delegates to the existing attach-mode codepath.
tryAttachExistingServernow accepts ANY validserver.lockregardless ofkind(the previous refusal ofmcp-spawnedlocks + the SIGTERM-and-replace branch are removed — lock kind is provenance-only, not a capability gate). Opening the desktop on a project with a live MCP-spawned server attaches instead of destroying the agent's session.Auto-update. A new
stopAllOwnedServers()replaces the previous synchronouskillAllUtilities(). BeforequitAndInstall, the desktop SIGTERMs each spawned detached pid in parallel, polls each lock for release every 200 ms for up to 10 s (sharingDEFAULT_SIGTERM_GRACE_MSwithok start's idle-shutdown UI-sibling termination), and escalates to SIGKILL per-pid on timeout. The hook isawaited beforequitAndInstallso ShipIt's pre-swappgrepcheck sees a clean process tree.Keepalive. The desktop main process holds a presence-invisible
/collab/keepaliveWS per open project — counts toward the server's idle-shutdown WS-client tally so a brief MCP disconnect (agent restart, IDE reload) can't trip idle-shutdown mid-session. Identity fields are omitted intentionally so the desktop is not redundantly rendered as a peer in the agent-presence bar.Process visibility.
ok startnow setsprocess.title = 'open-knowledge-server <projectName>'early in its action. The detached server is findable in Activity Monitor andps -ax | grep open-knowledge-server— the primary user-facing surface for orphan management, since there is no in-app stop UX (idle reaper handles routine cleanup; Activity Monitor handles wedged servers).CLI. Two new flags on
ok startenable the single-origin renderer model the desktop relies on:--serve-content-assets— serves content-directory assets (images, PDFs, file attachments) viacreateAssetServeMiddlewareon the same HTTP port as/api/*and/collab*.--react-shell-dist-dir <path>— serves a bundled React shell on/with SPA fallback (and auto-suppresses theok uisibling — when this flag is set, the server is self-sufficient for in-app-browser preview).
Terminal-launched
ok startwithout these flags retains today's two-process behavior (server +ok uisibling).Dev path unchanged.
bun run devinpackages/app/keeps the in-processhocuspocus-plugin.tsmodel (HMR + log capture ergonomics). Electron dev keepsutilityProcess.fork— thespawnDetachedServerdep is omitted in dev wiring; the WindowManager falls back toforkUtility.Shared primitives. The MCP shim's
startKeepaliveis lifted frompackages/cli/src/mcp/keepalive.tsto@inkeep/open-knowledge-core/keepalive/(itsMcpLoggertype reference is generalized to a structuralKeepaliveLoggerinterface). Both the MCP shim and the desktop main process now consume from the shared location. The CLI'sDEFAULT_SIGTERM_GRACE_MS+DEFAULT_SIGTERM_POLL_MSare lifted to@inkeep/open-knowledge-core/lifecycle-constantsso the desktop's auto-update teardown and the CLI's idle-shutdown UI-sibling termination stay in lockstep.Closes PRD-6665. Unblocks PRD-6664 (agent-side failure when server is down) and PRD-6513 (MCP tool completeness + agent harness integration) — both presupposed a server that outlives a single GUI session.
Spec:
specs/2026-05-21-desktop-detached-server-lifecycle/SPEC.md.MCP host entries are now written as
npx -y @inkeep/open-knowledge@latest mcpeverywhere, and existing Open Knowledge-managed entries are reclaimed to that canonical shape on Desktop boot orok start. The Desktop File-menu Command-Line Tools install surface was removed; usenpm install -g @inkeep/open-knowledgeornpx @inkeep/open-knowledgefor terminal access. On macOS, the publishedok mcpnow proxies to the installed Desktop bundle when available; suppress withok mcp --no-bundle-proxyorOK_BUNDLE_PROXY=0.
Patch Changes
fix(open-knowledge): keep the desktop beta channel sticky across stable promotions
A machine running a beta desktop build was a one-way door onto stable. The auto-updater derived its channel purely from the running binary's version (
channelFromVersion(app.getVersion())), and electron-updater's GitHub provider treats the beta channel as a superset of stable — a beta client walks the repo's singlereleases.atomfeed, picks the newest release of either channel, and cascadesbeta-mac.yml→latest-mac.ymlwhen that release is a stable one. So every stable promotion auto-updated beta machines onto the stable build. From that pointchannelFromVersionreturnedlatest,allowPrereleasewentfalse, and no future beta was ever visible again — there was no persisted channel preference and no way back.The auto-update channel is now sticky.
AppState.updateChannelis a persisted preference that latches tobetathe first time a beta build runs on a machine. The effective channel (resolveEffectiveChannel) isbetawhenever either the build OR the persisted preference isbeta, soallowPrereleasestaystrueafter a cascade-promotion onto a stable build and the machine re-boards the next beta release instead of being stranded on stable. Beta testers still ride onto stable releases (the intended cascade) — they just no longer fall off the beta track when they do.No new UI: the latch is automatic. There is no in-app beta→stable opt-out in this change; reverting to stable means installing a stable DMG and clearing the
updateChannelkey instate.json. A Settings channel toggle is future work.fix(open-knowledge): serialize concurrent writes to MCP host config files
writeEditorMcpConfig(the shared primitive that writes OK's MCP server entry into Claude Desktop / Cursor / Codex / VS Code / Windsurf config files) previously used a nakedfs.writeFileSyncinside a read-modify-write loop with no advisory lock and no atomic-write guarantee. Concurrent OK writers — CLIok initrunning while OK Desktop's startup-repair sweep is firing, two desktop instances launching against the same project, a double-clicked Add button in the consent dialog — could each observe the same pre-state, each computepre-state + my-entry, and the second write would clobber the first writer's addition. In the worst case,writeFileSyncinterleaves stripped every pre-existing MCP server entry from other tools (Cursor's, hand-edited, Codex's) — leaving only the last writer's single entry as the entire server map.A 20-process race reproduction (now committed as a regression test under
packages/cli/tests/integration/mcp-host-config-race.test.ts) showed lost updates in every trial, JSON corruption in one of five trials, and destruction of pre-existing entries in two of five. The bug class is the same one Anthropic's ownclaude-code#28966identified for.claude.jsonand recommends fixing with atomic writes plus a write lock.The fix wraps the read-modify-write block in a per-config-file advisory lock (
<configPath>.lockviawithFileLockSync, new in@inkeep/open-knowledge-core/server) and switches the underlying writes to a tmp+rename atomic pattern. The lock serializes OK writers across processes; the atomic write prevents external readers (Claude Desktop's config-load on launch) from observing a torn file mid-write.writeEditorMcpConfigkeeps its synchronous signature — the sync lock variant uses a bounded busy-wait sleep during retry rather than forcing an async cascade throughapplyProjectIntegrations,writeProjectAiIntegrations,repairMcpConfigs, and Desktop'swriteProjectMcpConfigarrow function. Typical lock-hold windows are sub-10 ms; the 5 s acquire timeout bounds the worst-case CPU spin.Fix Move-to-Trash for newly-created files in the sidebar (PRD-6769). Right-click → Delete on an Untitled file now correctly moves it to OS Trash instead of failing with a TrashFailureModal. Same fix applies to any freshly-renamed file whose Pierre tree path is still in the post-rename-strip extensionless state.
fix(open-knowledge/app): inline markdown images get click-to-zoom
Markdown
images sitting mid-prose (inline-position — image inside a paragraph that has other content) rendered as bare<img>tags with no lightbox affordance. Block-context![]()(image on its own line) already had zoom becauseimagePromoterPluginpromotes single-image paragraphs tomdxJsxFlowElement(CommonMarkImage), which routes through theimgdescriptor → ReactImage.tsx(withreact-medium-image-zoom's always-on<Zoom>wrap). Inline-position images deliberately stay as bare PMimagenodes (the promoter skips them — promoting an inline image to a flow-level component would split the paragraph), so they bypassed that surface entirely.The app layer now overrides
ImageSrcFidelitywith a React NodeView that wraps the inline<img>in the same<Zoom>lightbox, matching the descriptor side's configuration (wrapElement="span"for inline-flow fit;zoomMargin={20};zoomImg.sizescleared so the modal isn't constrained by a thumbnail-scopedsizesattribute). Block-context images, markdown round-trip, and PM-to-HTML serialization (clipboard, export) are unchanged — only the live editor render of the bare PMimagenode is replaced.Inline alignment is intentionally not surfaced: inline images live inside flow paragraphs, and
align="right"mid-paragraph would have to float out of the text-run to mean anything. Authors who want alignment move the image to its own line (which already gets the full block-context CommonMarkImage controls).