Skip to content

v0.41.0

Latest

Choose a tag to compare

@inkeep-internal-ci inkeep-internal-ci released this 27 Jul 14:17

Minor Changes

  • Broken-link handling is now configurable, and dead links get a one-shot fix. A new Content rules tab under Settings → This project holds two project-shared knobs: how broken internal links are reported — hidden, warning (the new default, since a broken link is often a typo or a page-yet-to-be-written), or error — and whether the file explorer tints and badges files that have problems. Both persist as validation.* in config.yml; lint plugins keep their own tab. In the Problems panel, every dead-link row (doc and project scope) now offers a Create page action that creates the missing target — the same action as the Links panel's missing-page affordance, surfaced where the problem is listed. It is deliberately not part of Fix all: bulk-creating targets could silently mint duplicate files, so creation stays a per-row decision.

  • Add Follow — a one-directional sync mode for people who follow a knowledge base they can read but not push to (share-link recipients and read-only followers).

    Previously, opening a repository you lacked push access to left your copy silently frozen: the sync onboarding prompt was suppressed, no sync config was written, and the engine parked itself the moment a push was denied. Follow fixes that dead end.

    What it does:

    • Keeps your local copy current automatically by fast-forwarding to the latest on GitHub, without ever pushing or committing on your behalf.
    • Lets your own uncommitted edits ride safely on top of incoming updates. Non-overlapping changes combine automatically; edits to the same lines the upstream changed surface in the familiar conflict view, where you choose keep-mine or take-theirs. Your branch always tracks the upstream tip and never forks.
    • Is always opt-in. Push-denied users are now offered Follow at first open with plain-language copy ("updates flow in from GitHub; your edits stay on this computer"), and anyone can choose Off / Follow / Full in Settings. A project owner can commit autoSync.default: follow so a fresh clone starts following.

    Also included:

    • Full-sync users who lose push access now get a "Switch to Follow" action on the sync-paused notice instead of a silent stall; switching keeps any unshared local commits as a local overlay.
    • The sync status badge stays visible for a followed project and shows a distinct following state (updating / up to date / conflict / offline).
    • Anonymous followers of public repositories poll more gently than signed-in followers.
    • A one-shot "Sync" pull that reports its outcome, for surfaces that need an on-demand refresh.

    Configuration note: the sync preference is now stored as a single autoSync.mode (off / follow / full). Existing autoSync.enabled settings keep working, and choosing a mode clears the legacy flag so the two can't disagree across versions. An app that predates autoSync.mode then reads the config as unanswered and re-prompts rather than acting on a stale toggle, so it never pushes for a mode you didn't consent to there.

  • Validate document frontmatter against JSON Schema files with the new frontmatter content-rules plugin.

    Schema mappings in config.yml. Enable the plugin in Settings → Plugins and map schema files to doc sets with globs — contentRules.frontmatter.schemas entries pair an appliesTo glob (or list; a leading ! excludes) with a file path. Schema files are ordinary draft-07 JSON Schema, readable by any external tool; .ok/schemas/ is only the default home.

    Warnings everywhere, blocking nowhere. Violations (frontmatter/required, frontmatter/enum, frontmatter/type, …) surface in the editor gutter, the Problems panel, ok lint, the MCP lint tool, and as advisories on agent writes — a violation never blocks or changes a write. Broken schema files and invalid globs report on the config channel (Settings panel, audit warnings), never as document problems.

    No-code editing. The plugin's settings panel manages the glob → file mappings, and a per-field editor edits each schema (type, required, allowed values, pattern) without touching raw JSON — keywords the editor doesn't model are preserved verbatim, and editing a mapping whose file doesn't exist yet creates it.

    Schema-aware property panel. Fields constrained by an enum render as a select, and array fields with items.enum render as toggleable value chips, so valid values are picked instead of typed.

    Agents learn the contract at read time. exec listings and reads advertise which schema files govern a doc or folder (schemas: …), resolved server-side.

    Discovery UX. The glob editor names its grammar with an example pattern, an explicit Edit button opens each schema on the WYSIWYG Fields view, and a glob that matches zero docs warns on the config channel alongside the invalid/suspicious-glob warnings. The Problems panel names the plugins doing the checking and, when none are enabled, says so and links to Settings → Plugins instead of a misleading "no problems".

Patch Changes

  • Fix cross-app clipboard fidelity for container-nested images that carry a non-portable (relative or document-relative) URL. Copying a component such as a Callout or Accordion that holds a relative-URL image no longer pastes the whole component's markdown source at the image's position in rich-text destinations. The image now contributes only its own source-fallback markdown.

    Previously the descriptor resolver climbed past the container's content boundary and treated the enclosing component as the leaf's descriptor, so the entire component source was serialized at the leaf's slot. The climb now stops at the container's content boundary, keeping the resolved range within the leaf's own node view. Copy as plain text and Open Knowledge to Open Knowledge paste were unaffected and remain unchanged.

  • Fix copying a rendered html preview code block pasting broken markup into other apps. Selecting and copying a preview-active code block previously placed the preview's internal sandboxed iframe (several KB of security-policy, theme, and bootstrap markup) plus resize-handle chrome into the clipboard, so pasting into Gmail, Notion, Slack, or Docs produced bloated, non-rendering junk. The clipboard now receives the clean fenced code source instead, matching how rendered math and Mermaid diagrams already copy.

  • Quiet the agent thread transcript: tool calls, permission prompts, and thoughts now signal by exception instead of decorating every row.

    Permission prompts lay out as at most three controls — refusal pinned far left, the least-privilege grant far right, and every escalating grant collapsed into a secondary split button beside it. Options are grouped by stance rather than by looking up one option per kind, since ACP treats kind as a styling hint and agents do offer several grants that differ only by name ("Allow for This Session" vs "Allow and Don't Ask Again"). The card is neutral rather than amber, and a settled prompt whose gated tool call is in the transcript now renders on that call's row instead of as a second card restating the tool name; a prompt whose call never appears keeps its standalone card so an outcome is never unreachable.

    Tool calls render collapsed, with failures the exception that opens itself. Completion is no longer badged — a call that finishes while you watch flashes a check that fades, and a replayed transcript carries no per-row status chrome at all, so the one row that failed is the one that stands out. Status still reaches assistive technology in every state. A row only draws its border when expanded, calls with nothing to reveal are no longer interactive controls, and consecutive calls sit tighter as a single burst of activity. An approval leaves no trace; a refusal says so in words, and only when the row's own status does not already explain it.

    Agent thoughts parse markdown instead of printing literal ** around the summary line agents prefix them with, with emphasis flattened so a thought stays quieter than the reply. Code renders one size down to sit optically level with surrounding prose, and a markdown fence wrapping a whole tool-output block no longer leaves its backticks on screen.

  • The launcher now leads with the same four actions whether or not you have recent projects.

    Previously a brand-new user got a different screen: a grid of starter-pack cards, with Open folder, Open file, Clone from GitHub, and Blank project demoted to a row of small links underneath. Anyone who came back after creating a project saw a layout that shared nothing with the one they had learned.

    Create new project, Open folder on disk, Open file on disk, and Clone from GitHub are now the primary actions in both states. When there are no recent projects, a subtle line takes the Recent list's place: the first three starter packs as pills, plus a count that opens a picker with the full set and their descriptions. Picking a pack from either place opens the create dialog with that pack selected, exactly as before.

  • Restored the native @parcel/watcher file watcher in the packaged desktop app (#760). The app runs ok start from a bundled CLI whose module resolver never reaches the app's own @parcel/watcher copy, so at server boot import('@parcel/watcher') failed with "Cannot find package" and the watcher silently fell back to chokidar. The macOS build now stages @parcel/watcher — its wrapper, the runtime dependencies it loads (picomatch, is-glob, is-extglob, detect-libc), and the per-architecture native binary — onto the CLI's own module path, so the fast native watcher loads as intended. Combined with the chokidar-fallback fix in the same release, external edits to files in subfolders are reflected in the graph, backlinks, and dead-link views without a restart. The staging resolves each dependency exactly as the wrapper loads it (the workspace carries two picomatch majors, so a naive copy could have shipped the wrong one), and a packaging test verifies the staged tree is CLI-resolvable without needing a full app build.

  • Link-target suggestions now match by name as you type, like the command palette.

    When editing a link target (the "Edit markdown link" dialog, the wiki-link panel, and the bubble-menu link popover all share the same field), typing a bare page name such as install now opens the suggestion dropdown and surfaces the matching page — a leading / is no longer required. Typing or pasting an external URL, or a bare #anchor, still does not pop a page list. The empty/browse list also keeps real content pages ahead of dot-directory files (.github/, .changeset/) so they no longer fill the list on projects rooted at a repository.

  • Fixed external edits to files in subfolders not being picked up when the server runs on its chokidar file-watching fallback (#760). The server prefers @parcel/watcher but falls back to chokidar whenever that native module can't load. The fallback's ignore predicate misclassified any subdirectory that chokidar probed without file stats — routing it through the file-only exclusion rule, which rejects anything that isn't a Markdown doc or linkable asset — so chokidar pruned every content subfolder and silently stopped watching it. The result: editing a note in a subfolder with an external editor (or a git pull landing changes there) never reached the server, and the graph, backlinks, and dead-link views stayed stale until the next restart re-read everything from disk. The predicate now resolves whether a stats-less path is a directory before deciding, so subfolders are watched again; a path that can't be stat'd (for example a file deleted mid-scan) is admitted rather than pruned, so its delete still propagates. Excluded trees such as node_modules/ and .git/ stay pruned as before.

  • Make custom embeddings endpoints work from the desktop app: auto-detected vector size, a model field, and a Test connection button.

    Semantic search previously assumed every model returned 1536-dimension vectors. Pointing it at a self-hosted or non-OpenAI OpenAI-compatible endpoint whose model has a different native size made every response fail validation, and the failure was silent — search just quietly fell back to keyword matching. Leaving search.semantic.dimensions unset now means "use whatever the endpoint returns": the size is detected from the first response, pinned in the vector cache, and reused across restarts without re-embedding. Setting dimensions explicitly keeps the strict check, so a server that ignores the request parameter still fails loudly instead of silently. If a provider starts returning a different size (a model swapped behind an alias), the cached vectors are discarded and rebuilt at the new size rather than scoring two sizes against each other.

    ok embeddings set-model <id> / clear-model join set-url / clear-url, so a headless setup can name the model its endpoint serves without hand-editing config.yml.

    Embeddings API keys are now per project. The key moved from a single machine-global value (edited in Settings → Account) to a per-project, per-endpoint key edited on the same Settings → This project → Search screen as the endpoint it belongs to — no more hunting across two screens. Keys still live only in the 0600 ~/.ok/secrets.yml (never in the project tree), now keyed by project + endpoint so a key can never travel to a host it wasn't set for. A localhost endpoint (Ollama / LM Studio) needs no key at all. Existing single-key OpenAI setups keep working untouched — the old flat key is read as a fallback for the default OpenAI endpoint. ok embeddings set-key / clear-key are now project-scoped (add --cwd), and ok embeddings list shows every stored key (redacted).

    Settings → Search gains a "Custom endpoint" section holding the endpoint URL, a free-text Model field, and a Test connection button that runs one throwaway embed and reports either the detected vector size or a specific reason (no key, unreachable, HTTP 401, wrong response shape, and so on). The section expands automatically when either value is already overridden, and changing either one now warns that it re-embeds and re-sends the whole corpus before committing. The Account key control is relabelled "Embeddings API key" to reflect that it is sent to whichever endpoint a project has configured, with a note that local servers needing no key accept any placeholder.

  • Pull the latest changes without leaving OpenKnowledge when a share link arrives ahead of your copy.

    When someone sends a link to a doc that is already on GitHub but has not reached your local copy yet, the receive dialog used to say "pull the latest changes, then open the link again" and leave you to go do it in a terminal. It now offers "Pull latest changes" on the spot: OpenKnowledge fetches and fast-forwards your copy in place, then opens the shared doc once the update lands. It only ever pulls — nothing is committed or pushed on your behalf, and your uncommitted edits ride on top of what comes in. If the pull can't run, because another sync operation is already in flight or GitHub is unreachable, the reason appears under the message and the button stays available to retry.

    When that pull lands on a project whose sync is off, OpenKnowledge then asks whether to keep the copy updated, so later links arrive on a copy that is already current. It is the usual Follow confirmation, asked once the pull has proved its worth rather than offered as a second button beside it: confirm and Follow turns on before the shared doc opens, decline and the doc opens anyway. You are asked at most once per session, and never after a pull that failed or brought a conflict with it.

    The button and the question both appear in the dialog you get before a tab opens and in the in-tab panel behind it, and both stand down in favor of the previous guidance when the project has no GitHub remote or its sync is mid-conflict.

  • The user-global Agent Skill now installs only into agent hosts you actually have, and nothing is written when you have none.

    Previously ok init shelled out to npx -y skills@~1.5.0 add … --agent '*' -g -y --copy. The --agent '*' flag makes that CLI skip its own host detection and write into every host in its registry, so a single ok init created 110 directories across 54 tool-config homes in a real $HOME — 51 of them for tools that had never been installed, including .zencoder, .terramind, .rovodev, .snowflake/cortex and .tabnine/agent. Because OK's user-global skills are behavioural instructions autonomous software reads and acts on, that is a scope-of-consent problem, not cosmetic clutter. Neither --no-mcp nor --local-only suppressed it, and OK_RECLAIM_DISABLE / OK_SKILL_MANAGE gate a different code path, so there was no way to narrow it short of the undiscoverable --no-skills.

    The install no longer runs a subprocess at all. OK copies the bundle itself into the skills directory of each detected host — the same detectPath probe ok init already uses to decide which editors get MCP wiring — plus the shared ~/.agents/skills/ hub those hosts read natively. With no host detected it writes nothing, not even the hub. Nothing about the dependency was load-bearing: OK passed a local path, forced --copy, and that CLI writes no lockfile or state at global scope, so it contributed a directory table OK already maintains in core for project scope. Dropping it also removes a floating-range npx -y fetch-and-execute at init time and third-party install telemetry (add-skill.vercel.sh) that OK never opted out of, and makes the install work where npx isn't on PATH.

    Coverage narrows from that CLI's ~75 hosts to the ones OK supports and tests — Claude Code, Cursor, Codex, and OpenCode — which is the same set ok init already wires MCP for.

    ok repair-skills now also cleans up after the old behaviour. It removes OK's own open-knowledge* skill directories from the agent homes a pre-0.42 fan-out reached but OK never supported, and then removes those agent homes themselves when OK's skill was the only thing in them — otherwise the fix would leave ~47 empty directories behind and a home-directory diff would look much as it did before. On a home left by the old build that is 54 top-level entries down to 9.

    The prune is not conditional on there being a skill to delete: a machine swept by an interim build has the skills already gone and only empty ~/.<host>/skills/ husks left, and keying the walk off a fresh deletion would make those unreachable forever. ok repair-skills removes them on their own.

    Deleting from $HOME is exactly what issue #820 was about, so the cleanup asks first. It prints every path it would remove, says plainly that each agent home is going because it holds nothing once OK's skill is gone, and waits for confirmation; a bare Enter declines. --yes skips the prompt, and a non-interactive run without it declines rather than deleting unattended. It is deliberately not wired into ok init.

    It also refuses to descend through a symlink: agent hosts commonly symlink their skills/ dir at the shared ~/.agents/skills hub, and following one could delete a live install — or a user directory — through the link, so any symlinked ancestor disqualifies that host entirely. applyLegacyFanoutSweep re-derives the set of paths it is allowed to touch and aborts without deleting anything if handed one outside it, and both entry points reject a non-absolute home (an empty one would silently retarget the sweep at the process's working directory).

    Three further bounds keep it conservative: only directories named in a frozen list of the agent homes that one shipped version wrote to (never a path discovered by walking the filesystem), only directories that are empty afterwards — a tool you actually use keeps its folder, and the removal goes through rmdir, which refuses a non-empty directory even if something appears between the prompt and the delete — and never a shared root, so ~/.config/goose goes while ~/.config stays.

    The ok init summary now names the hosts it wrote to (installed for Claude, Cursor) instead of claiming installed to detected agent hosts while detection had been bypassed, reports when no host was found, and points failures at ok repair-skills rather than a manual npx command. The @inkeep/open-knowledge package no longer declares a postinstall script; it never worked (the symbol it imported was never exported from the published bundle) and would have fanned out with no consent gate had it been repaired.