v0.6.0
Part of the OpenKnowledge changelog.
Minor Changes
Make GitHub-sync conflict state a structural gate on every mutating write surface — human and agent.
Editor. When a doc enters a merge-conflict state the editor area swaps from the normal editor to a side-by-side
DiffView; Y.Text is structurally inaccessible to typing while the conflict is unresolved. The conflicted tab gains a⚠badge, and a pinned ⚠ Conflicts section auto-appears at the top of the file tree (auto-hides when the conflict count reaches zero). All UI "Keep mine" actions dispatch asstrategy: 'content', content: serializeDoc(docName)— what the user saw IS what gets written, eliminating the byte-divergence class where pre-conflict unflushed edits would silently mix with the merge result. The legacy side-sheetConflictResolveris removed; the editor-area DiffView is now the sole UI resolution surface.MCP. Three new tools —
list_conflicts,get_conflict_content,resolve_conflict.read_documentalways returnslifecycle: {status, reason} | nullso agents can detect conflict state proactively. Every mutating MCP handler (write_document,edit_document,delete_document,rename_document,rollback_to_version,write_template,delete_template, agent undo) refuses against a doc in conflict with the slim RFC 9457 envelopeurn:ok:error:doc-in-conflict(409); a meta-test scans every mutating route handler and fails CI if a new handler omits the gate.Server. Block-level reconciliation failures (
case 'conflicts') now setlifecycle.status='conflict'on the doc — closing the bypass where these failures previously skipped the editor / MCP gates. A new boot-time scan (restoreLifecycleFromConflictsJson) reads<projectDir>/.ok/local/conflicts.jsonbefore the HTTP server accepts connections and pre-seedslifecycle.status='conflict'on every tracked doc, so an in-progress conflict survives a server restart.Recovery procedure for stuck conflicts is documented in
RUNBOOK.md. There is no server-exposed clear endpoint by design — the documented procedure is the escape hatch.Spec:
specs/2026-05-19-conflict-aware-write-surfaces/SPEC.md. Supersedes FR27 + FR33 ofspecs/2026-04-14-github-sync/SPEC.md.feat(open-knowledge): consolidate the MCP tool surface 29 → 17, split the bundled skill, and slim the project SKILL.md.
Skill split. The single bundled Agent Skill is split into
discovery(slim, user-global — no behavioral rules) andproject(rich, project-local — the full agent-runtime contract).MCP tools: 29 → 17. Facet clusters are merged behind a discriminator parameter — every merge single-risk-level (all-read or all-write), zero capability loss:
linksreplaces the six link-graph getters (get_backlinks,get_forward_links,get_dead_links,get_orphans,get_hubs,suggest_links) behind akinddiscriminator.versionreplacessave_version+rollback_to_versionbehind anactiondiscriminator;get_historystays a separate read tool.folder_configreplacesset_folder_rule+write_template+delete_templatebehind anactiondiscriminator.renamereplacesrename_document+rename_folder— the tool resolves whether the target is a doc or a folder.frontmatter_patchis renamededit_frontmatter, kept as its own tool (a clear parallel to body-onlyedit_document); input schema and RFC 7396 merge-patch semantics are unchanged.read_document,grep, andlist_documentsare dropped —execsubsumes them (cat/grep/ls).exec'slsenrichment is extended to surface folderfrontmatter_defaults+templates_available, so droppinglist_documentsloses nothing.
Dead code from the removed tools is cut; every surviving tool's
DESCRIPTIONis slimmed.project/SKILL.mdslimmed 464 → 381 lines (de-duplication only — every load-bearing MUST/STOP rule retained); the generated MCPinstructionsecho is regenerated.PRD-6704 fix.
ok ui'salready-runninglock-collision branch now keeps the process alive until SIGTERM when run non-interactively, instead ofprocess.exit(0)— Claude Desktop'spreview_startno longer fails "Process exited unexpectedly". The SKILL.md preview guidance is corrected to callpreview_startand never edit.claude/launch.json.This is a breaking change to the MCP tool surface — agents or automation that hardcoded the retired tool names must move to the merged tools (old → new map in
specs/2026-05-20-ok-mcp-tool-consolidation/SPEC.md§9.2). OK is pre-1.0; bump isminor.Spec:
specs/2026-05-20-ok-mcp-tool-consolidation/SPEC.md.save_versionandrollback_to_versionno longer create commits or tags in the user's parent git repository. They write only to the Open Knowledge shadow repo —git blame,HEAD, the index, andok/v<N>tags are left untouched on the user's real.git/. The history timeline, TimelinePanel, and the Restore button continue to work as before via the shadow checkpoint.Removed schema fields:
SaveVersionRequest.principal— was used to override the parent-git commit author identity.SaveVersionRequest.message— was used as the parent-git commit subject.SaveVersionSuccess.versionTag— was the optionalok/v<N>tag created on the parent-git commit.
If you were passing
principalormessagetoPOST /api/save-version, the fields are now silently ignored. If you were readingversionTagfrom the response, the field is no longer present.
Patch Changes
fix(open-knowledge): bump @napi-rs/keyring 1.2.0 → 1.3.0 to fix repeated macOS keychain prompts
Users signing in to GitHub from the desktop app saw the macOS keychain "Always Allow" dialog re-appear successively during a single sign-in flow. Clicking Allow did not suppress subsequent prompts within the same flow.
Root cause:
@napi-rs/keyring@1.2.0wrapskeyring-rsv3, which on macOS calls the legacySecKeychain*API. Items created via that API are gated by per-binary ACL entries — thekSecKeychainPromptUnsigned/kSecKeychainPromptInvalidheuristics surface a fresh prompt whenever a different executable in the bundle (main process vs utility-process fork vs spawned CLI subprocess) touches the same item, and "Always Allow" only applies to the operation/binary combination that surfaced it. The maintainer ofkeyring-rscalls this out directly in #272: "You really shouldn't be using the legacy keychain. Switch tokeyring-core+apple-native-keyring-store."@napi-rs/keyring@1.3.0does exactly that — its Cargo deps swapkeyring = "3" (apple-native)forkeyring-core = "1.0" + apple-native-keyring-store = "1.0", which calls the modernSecItem*APIs withkSecAttrAccessGroupscoping. Items are now gated silently by Team ID (6NZGSG335T) + bundle identifier instead of per-binary ACL, so a sign-in flow surfaces zero prompts.The npm package shape is unchanged between 1.2.0 and 1.3.0 (same optionalDependencies, same per-arch package names, same
cpu/osgates, samekeyring.<arch>.nodebinary filenames), sopackages/desktop/scripts/prepare-universal.mjskeeps working untouched. The Node-facing API (Entry/setPassword/getPassword/deletePassword) is unchanged — verified by the fullpackages/cli/src/auth/test suite (46 pass) andpackages/desktop/src/utility/keyring-smoke.test.ts(7 pass) against the new binding.Users on 0.5.0 or earlier who had a token stored under the legacy keychain will re-authenticate on first launch under 1.3.0 — the modern data-protection keychain is a different storage layer and does not see the legacy items. This is a one-time cost; subsequent launches are silent.
One residual:
clearTokenFromAllBackendsresolves aKeyringBackendfrom the runtime@napi-rs/keyring, so under 1.3.0 it can only sweep modern- keychain entries on signout. The legacy entry written by ≤0.5.0 persists in the user'slogin.keychain-dbas an unreferenced stale token (already revoked after first re-auth — the user re-authed against GitHub, which implicitly invalidates the prior session). Users can remove it manually via Keychain Access if desired. The file backend is swept as before. Not adding a one-shot legacySecKeychain*delete on first launch because that sweep would itself trigger the prompt cascade this PR fixes.fix(open-knowledge): recognize Claude Cowork as Claude in agent presence
Claude Cowork connects to the Open Knowledge MCP server with a clientInfo.name of local-agent-mode-
rather than claude-code. That string was in no client registry, so a Cowork-driven agent rendered with the generic Sparkles "Agent" icon, showed its raw local-agent-mode-open-knowledge string as the display label in the presence bar, timeline, and agent-activity panel, and was tagged as bot in telemetry. iconFromClientName and resolveAgentType now recognize the local-agent-mode-* prefix as Claude. A new displayNameFromClientName helper resolves a clean brand name for known clients and feeds AgentIdentity.displayName, so no agent surfaces its raw clientInfo.name anymore (this also fixes claude-code rendering as the literal string "claude-code").
Because AgentIdentity.displayName also flows to the agentName field of write operations, contributor and git-commit attribution text now records known clients under their brand name (claude-code as Claude, cursor as Cursor) instead of the raw clientInfo.name. Telemetry agent_type is unaffected because resolveAgentType still returns the stable enum.
fix(open-knowledge): consistent spacing, description sizing, and footer buttons across dialog forms
Polished pass over the dialog/form surfaces so spacing and typography match across the app:
- Field helper/description text is now
text-1smeverywhere via the sharedFormDescriptionandFieldDescriptionprimitives (plus the dialogs that build forms ad-hoc). - Label-to-input-to-description spacing is
gap-2; spacing between form items isgap-6(FieldGroupand per-dialog form containers aligned). - Footer Cancel/Close buttons use the
outlinevariant withfont-mono uppercaseconsistently, replacing the mix ofghostand unstyledoutlinebuttons. TheDialogFooterandDialogCloseprimitives carry the same styling. - The Trash delete-confirmation detail line ("You can restore this file from the Trash.") is now
text-sm.
Affected surfaces include Create Project, Consent, New Item, Publish to GitHub, Share Receive, Install in Claude Desktop, template create/edit, Seed, Clone, GitHub sign-in, and the sync confirmation dialogs. No behavior changes.
- Field helper/description text is now
fix(open-knowledge): install the project-local runtime skill from OK Desktop project setup
Creating or opening a project in OK Desktop now installs the project-local
open-knowledgeruntime skill (.claude/skills/open-knowledge/,.cursor/skills/open-knowledge/,.agents/skills/open-knowledge/) — parity withok init.Previously the Desktop project-setup path wired the MCP server but never created the project skill. The skill split moved the agent's runtime contract (STOP rules, the preview-attach handshake, MCP tool routing) from a user-global skill to a project-local one, and
writeProjectAiIntegrations— the function both Desktop project-setup paths call — wrote MCP config only. A Desktop-primary user who never ranok initfrom a terminal got a project with MCP wiring but no agent behavioral contract.Per-editor project setup is now a single composable layer: a
ProjectIntegrationWriterinterface, two writers (mcp-configandproject-skill), and anapplyProjectIntegrationsorchestrator.writeProjectAiIntegrationsdelegates to it, so both Desktop project-setup paths install the MCP config and the project skill through the same shared code.Open Knowledge skill and MCP tool guidance: the agent now navigates the live preview to the primary deliverable (hub or overview) at the end of a turn that created docs, instead of leaving it on its starting page. Dead links are no longer tolerated — the consolidate, research, and discover tool guides and the runtime skill now require every link to resolve to an existing doc; a doc you want but should not create yet is tracked via a task, not left as a broken link.
fix(open-knowledge): warn when write_document(prepend|append) is handed frontmatter
A
---frontmatter block in aprependorappendpayload is intentionally dropped by the CRDT write path — a second frontmatter block would create invalid double-frontmatter. Previously it was dropped silently.write_documentnow adds a note to the response when a prepend/append payload contains frontmatter, pointing the agent atedit_frontmatter(1-2 keys) orwrite_document({ position: "replace" })(full rewrite). Addresses PRD-6660.feat(open-knowledge): route-only previewUrl + dedicated get_preview_url tool
MCP-contract change to take the preview base/port off every tool response so agents stop reconciling ports that sit in front of every payload (the live UI behind the Claude Code lock-collision proxy is reachable only on the proxy port, never the one previously baked into
previewUrl).previewUrlis now route-only (/#/<doc>, no scheme/host/port). It still rides every read/write response, so the attach-preview-once warning flow is unchanged — only the base leaves the payload.- The top-level
uiblock (ui.baseUrl/ui.port) is removed fromexec/search/linksresponses;search's output schema dropsui. - New MCP tool
get_preview_urlreturns the browser-reachable URL on demand ({ url, baseUrl, running }) — for hosts that open the URL themselves, with a clear "no UI running" result when none is. Surface count goes 17 → 18.
The runtime skill and generated MCP instructions describe the v4 contract: capability-based host detection (a
preview_*tool →preview_start; otherwiseget_preview_url→ the host's own in-app browser; system browser only as a true last resort, stated when used), opening/reading a file is a preview navigation to that doc's route, plus the PRD-6735 skill-clarity fixes (Codex CLI has no in-app browser; the generic snapshot/eval/screenshot loop does not apply to OK's read-only one-way preview).External consumers that read a full
previewUrlorui.baseUrlmust switch toget_preview_url.Release notes on GitHub Releases no longer leak Changesets' internal per-package version bullets (e.g. stray
@inkeep/open-knowledge-core@0.5.0-beta.6lines that didn't match the actual published-beta.N). These were boilerplate cross-references emitted by Changesets in pre mode; only narrative entries authored by changeset files are shown now.feat(open-knowledge): branch-aware share-link receive flow
Open Knowledge share links now land receivers on the branch the share was created from. Previously the branch field encoded in the URL was parsed but never consumed — receivers without a local clone got the remote's default branch, and receivers with an existing clone got whatever branch was already checked out, often opening a doc that didn't exist on their current branch with no signal that anything was wrong.
The clone path (
ok cloneand the desktop Clone CTA) now passes-b <share-branch>togit clone, falling back to the default branch with a toast if the share's branch is gone upstream. The existing-clone path detects branch mismatch by reading.git/HEADdirectly (no server needed) and falls through to a branch-switch dialog with four state-matrix variants (file exists on current branch / file missing on current branch, working tree clean / dirty conflict). Switching is performed via the newPOST /api/git/checkoutendpoint and dialog dismissal is gated on the CC1branch-switchedsignal landing in the project window so the doc opens on the new branch only after the Y.Doc transition has settled.Branch encoding in shared GitHub blob URLs is now percent-encoded as a single path segment, so slashed branch names (
feat/foo) round-trip correctly through builder and parser.Back-compat: share URLs without a branch field continue to work; the
ok:deep-linkIPC payload change is additive ({doc, branch?}).fix(open-knowledge): stop the runtime skill from inviting browser screenshots
The preview section of the runtime skill (and the generated MCP instructions) told the agent to "navigate to verify edits landed" while elsewhere telling it not to screenshot. On an in-app browser, navigating gives the model no visual feedback — so "verify" silently expanded to "navigate then screenshot", the exact loop the skill forbids. The contradiction made agents burn image tokens re-confirming edits the CRDT tool response had already confirmed.
The skill now frames the preview consistently as the user's live window, not an agent verification surface: the agent's confirmation that an edit landed is the tool response, navigation only positions the user's view, and re-navigation happens only on a user request to open a different doc. The screenshot exception list drops the fuzzy "a response looks ambiguous" loophole — it is now visual-rendering debugging or an explicit user request, never edit confirmation.
feat(open-knowledge): add Link to a page and External Link slash-command items
The editor's
/palette now offers two link entries under the Insert group:- Link to a page re-triggers the existing
[[page picker (WikiLinkSuggestionMenu) by inserting the[[delimiter, so page search, anchor mode (page#heading), and create-new-page all work exactly as they do when typing[[by hand. (wiki/[[still match in search.) - External Link lands a placeholder
linkchip carrying alinkmark (seeded with anhttps://scheme so the URL editor renders and pre-fills the scheme), then auto-opens the link editor focused on the URL field — no hover-then-click-Edit step. Auto-open reuses the existingsetPendingAutoOpen-style flag pattern: the slash command flags the freshly inserted mark id andInternalLinkPropPanelconsumes the flag on mount, opening its edit dialog.
Both entries are hand-authored inline slash items (
getInlineComponentItems), alongside the existingTagentry.- Link to a page re-triggers the existing
write_documentno longer requirespositionwhen creating a new document. An omittedpositionnow defaults toreplacefor a doc that does not exist yet —replace,append, andprependare identical on empty content, so the common "create a doc" call (write_document({ docName, markdown })) just works without the extra round-trip.positionis still required when writing to a doc that already exists: omitting it returns a clear error rather than silently overwriting content the caller did not mean to touch. Passingpositionexplicitly is unchanged.