OpenKnowledge

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 as strategy: '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-sheet ConflictResolver is removed; the editor-area DiffView is now the sole UI resolution surface.

    MCP. Three new tools — list_conflicts, get_conflict_content, resolve_conflict. read_document always returns lifecycle: {status, reason} | null so 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 envelope urn: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 set lifecycle.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.json before the HTTP server accepts connections and pre-seeds lifecycle.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 of specs/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) and project (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:

    • links replaces the six link-graph getters (get_backlinks, get_forward_links, get_dead_links, get_orphans, get_hubs, suggest_links) behind a kind discriminator.
    • version replaces save_version + rollback_to_version behind an action discriminator; get_history stays a separate read tool.
    • folder_config replaces set_folder_rule + write_template + delete_template behind an action discriminator.
    • rename replaces rename_document + rename_folder — the tool resolves whether the target is a doc or a folder.
    • frontmatter_patch is renamed edit_frontmatter, kept as its own tool (a clear parallel to body-only edit_document); input schema and RFC 7396 merge-patch semantics are unchanged.
    • read_document, grep, and list_documents are dropped — exec subsumes them (cat / grep / ls). exec's ls enrichment is extended to surface folder frontmatter_defaults + templates_available, so dropping list_documents loses nothing.

    Dead code from the removed tools is cut; every surviving tool's DESCRIPTION is slimmed.

    project/SKILL.md slimmed 464 → 381 lines (de-duplication only — every load-bearing MUST/STOP rule retained); the generated MCP instructions echo is regenerated.

    PRD-6704 fix. ok ui's already-running lock-collision branch now keeps the process alive until SIGTERM when run non-interactively, instead of process.exit(0) — Claude Desktop's preview_start no longer fails "Process exited unexpectedly". The SKILL.md preview guidance is corrected to call preview_start and 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 is minor.

    Spec: specs/2026-05-20-ok-mcp-tool-consolidation/SPEC.md.

  • save_version and rollback_to_version no 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, and ok/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 optional ok/v<N> tag created on the parent-git commit.

    If you were passing principal or message to POST /api/save-version, the fields are now silently ignored. If you were reading versionTag from 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.0 wraps keyring-rs v3, which on macOS calls the legacy SecKeychain* API. Items created via that API are gated by per-binary ACL entries — the kSecKeychainPromptUnsigned / kSecKeychainPromptInvalid heuristics 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 of keyring-rs calls this out directly in #272: "You really shouldn't be using the legacy keychain. Switch to keyring-core + apple-native-keyring-store."

    @napi-rs/keyring@1.3.0 does exactly that — its Cargo deps swap keyring = "3" (apple-native) for keyring-core = "1.0" + apple-native-keyring-store = "1.0", which calls the modern SecItem* APIs with kSecAttrAccessGroup scoping. 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/os gates, same keyring.<arch>.node binary filenames), so packages/desktop/scripts/prepare-universal.mjs keeps working untouched. The Node-facing API (Entry / setPassword / getPassword / deletePassword) is unchanged — verified by the full packages/cli/src/auth/ test suite (46 pass) and packages/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: clearTokenFromAllBackends resolves a KeyringBackend from 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's login.keychain-db as 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 legacy SecKeychain* 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-1sm everywhere via the shared FormDescription and FieldDescription primitives (plus the dialogs that build forms ad-hoc).
    • Label-to-input-to-description spacing is gap-2; spacing between form items is gap-6 (FieldGroup and per-dialog form containers aligned).
    • Footer Cancel/Close buttons use the outline variant with font-mono uppercase consistently, replacing the mix of ghost and unstyled outline buttons. The DialogFooter and DialogClose primitives 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.

  • 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-knowledge runtime skill (.claude/skills/open-knowledge/, .cursor/skills/open-knowledge/, .agents/skills/open-knowledge/) — parity with ok 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 ran ok init from a terminal got a project with MCP wiring but no agent behavioral contract.

    Per-editor project setup is now a single composable layer: a ProjectIntegrationWriter interface, two writers (mcp-config and project-skill), and an applyProjectIntegrations orchestrator. writeProjectAiIntegrations delegates 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 a prepend or append payload is intentionally dropped by the CRDT write path — a second frontmatter block would create invalid double-frontmatter. Previously it was dropped silently. write_document now adds a note to the response when a prepend/append payload contains frontmatter, pointing the agent at edit_frontmatter (1-2 keys) or write_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).

    • previewUrl is 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 ui block (ui.baseUrl / ui.port) is removed from exec / search / links responses; search's output schema drops ui.
    • New MCP tool get_preview_url returns 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; otherwise get_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 previewUrl or ui.baseUrl must switch to get_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.6 lines 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 clone and the desktop Clone CTA) now passes -b <share-branch> to git 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/HEAD directly (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 new POST /api/git/checkout endpoint and dialog dismissal is gated on the CC1 branch-switched signal 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-link IPC 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 link chip carrying a link mark (seeded with an https:// 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 existing setPendingAutoOpen-style flag pattern: the slash command flags the freshly inserted mark id and InternalLinkPropPanel consumes the flag on mount, opening its edit dialog.

    Both entries are hand-authored inline slash items (getInlineComponentItems), alongside the existing Tag entry.

  • write_document no longer requires position when creating a new document. An omitted position now defaults to replace for a doc that does not exist yet — replace, append, and prepend are identical on empty content, so the common "create a doc" call (write_document({ docName, markdown })) just works without the extra round-trip.

    position is 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. Passing position explicitly is unchanged.

View v0.6.0 on GitHub