v0.71.0
Minor Changes
Make uninstall preserve symlinked configuration files and ignore rules for shared editor settings. Wait for servers to exit before deleting their state, retain enough state to retry incomplete cleanup, and report unresolved integrations as failures. On macOS, finish desktop cleanup after the application exits so shutdown cannot recreate its settings. When removing an agent connection in Settings, preserve the reason for a cleanup failure in the OpenKnowledge log. Honor custom Pi agent directories when removing project trust, and distinguish configuration read failures from invalid syntax during init.
Scripts using
ok deinitorok uninstallshould handle exit code1when integration cleanup remains unresolved. With--yes --json, those items now appear infailed[]instead ofskipped[]; repair the reported problem and retry.skipped[]still covers intentionally preserved entries, including another tool's configuration and dangling shell, editor, or launch config symlinks. A dangling Pi bridge reports a failure. Cleanup also fails for an unreadable, dangling, or cyclic trust store when an owned bridge or recorded grant requires checking it. Bridge setup and cleanup refuse project paths they cannot resolve or access. For a missing project, cleanup checks records under the supplied path and the missing suffix appended to the nearest accessible ancestor's current real path, following healthy directory aliases only for that additional lookup. With no bridge or record at either path, the project is already absent. Known records or an unresolvable or inaccessible ancestor require repairing the project path before retrying. This lookup cannot recover historical symlink targets. Setup also refuses a symlink target that differs from the real directory shown in its approval prompt, before changing the bridge or trust settings. With neither a bridge nor a record and a verifiable project path, the store is unrelated and is not inspected.For callers of the exported JavaScript API,
probePiBridgeState()can now returnproject: 'unavailable'with an error. Narrow toproject === 'ready'before readingbridge,trust,bridgeLoadable,canonicalCwd, orotherExtensions.otherExtensionscontains quoted display names with control characters escaped, not raw filenames.ensurePiBridge()can now returnbridge: 'refused-project-path'when the project path cannot be verified or differs from the approved directory; handle that refusal before using the bridge. CustomEditorMcpTargetobjects withformat: 'file'must provide aprojectConfigPathfunction.Pi trust updates coordinate with Pi's own file lock and preserve symlinked stores. New trust changes record the previous folder decision so cleanup can restore it, using the real project directory even when opened through a symlink. Every grant made before this release lacks an ownership record and is preserved, including grants made by OpenKnowledge; there is no automatic backfill. Older entries under non-canonical project spellings, such as symlink paths, are also preserved and named when found. Pi does not use those literal keys, and cleanup does not move them to the real-path key. Interrupted setup records also preserve trust because completion cannot be verified, and retries do not reconcile them while the folder remains trusted. To remove an unrecorded or non-canonical grant manually, close Pi and remove only the project's exact-folder key from its
trust.json; the cleanup message names an entry's key and file when it can inspect that entry. If the project path is already unavailable when cleanup starts and ownership records can be read, cleanup names those records and recorded store locations without claiming those grants still exist. If a later project-path check fails, the affected trust store is left pending with guidance to repair the path and retry. For manual cleanup using a record, inspect the intended destination and restore the previous decision only if you choose to undo a current true entry; remove that key only when there was no previous entry. Once the project's.ok/directory is gone,ok deinitreports nothing to remove; use that manual cleanup for any retained entries. Other entries, parent-folder decisions, and Pi's global trust default remain intact and may still confer trust.When other Pi resources still need folder trust, cleanup lists up to three resources and the remaining count, and relinquishes its ownership record where one exists. Removing the retained grant manually may stop Pi from loading those resources. Later cleanup runs will not revoke that grant, even after those resources are removed. When a bridge or record requires trust cleanup, OpenKnowledge checks the currently configured store and saved locations for recorded grants, including custom Pi directories. It reports retained exact keys it finds, including the supplied project spelling when it differs from the real path. Unknown historical stores and aliases are not searched or inferred. Broken trust files also block bridge setup until repaired. The Pi-compatible directory lock coordinates with Pi at the configured path; the separate OpenKnowledge lock coordinates its own writers across symlinks to the same destination.
Patch Changes
Turning an option off in a per-agent setup dialog now reads as the removal it is.
Unticking a box and pressing Save deletes that MCP entry or skill file, the same outcome the Remove button produces. Remove asks first and lists what goes; Save said "Save changes" in the ordinary style and deleted without further comment, so the two routes to one deletion were guarded very differently.
The dialog footer now counts the two directions separately ("1 added", "1 removed", or "1 added · 1 removed") instead of reporting a single change count, and the confirm button takes its destructive styling whenever the draft turns something off. When every change is a removal the button says Remove rather than Save changes. Turning every option off adds the sentence that names the consequence: "This disconnects Cursor from OpenKnowledge."
All of that sits in the footer rather than in the dialog body. The body scrolls and the footer does not, so a reader who has scrolled down through the options can still see what pressing the button will do.
The Ask AI composer at the bottom of the editor now takes attachments. Drop a file on it, paste a screenshot into it, or use the new attach button beside the field; each attachment appears as a removable chip above the input and rides along with your prompt into the new chat.
- Only in-app agents receive attachments. A terminal CLI or an external app has no attachment channel, so with one of those selected the composer refuses the drop and says which constraint applies rather than accepting a file it cannot forward. Attach first and then switch agents, and the send is blocked with the same explanation instead of quietly stripping the file.
- Images from anywhere, other files from inside the project. A non-image file is sent as a path reference, so it has to live in the workspace; anything skipped is reported as a single summary rather than one notice per file. Attachments are capped at 700 KB per message — a full-screen Retina screenshot is usually over that limit, so crop or resize it first.
- The agent gets the last word on images. If the agent turns out not to accept image attachments, the chat now says so instead of leaving you to guess why the reply ignored your screenshot.
The create-a-project prompt on a fresh project takes attachments the same way: drop, paste, or attach while an in-app agent is picked, and the files ride into the launched conversation; pick a terminal CLI or an external app instead and it refuses with the reason. The comment composers that share this field answer a dropped file with a short inline note instead of ignoring it.
A frozen table header no longer drops out of its pinned position for one frame when its freeze range is recomputed. The header cell's pin is driven by scroll-driven Web Animations, and the extension replaces those animations whenever the freeze key changes. A replacement was play-pending on the frame it was created:
currentTimewasnull, its effect was not in effect, and nothing else set the cell's transform, so the computed transform wasnonefor that frame and the header could fall as much as roughly 700 px before snapping back.Each replacement now has its start time assigned when it is created, which resolves its pending play task, so it is in effect on the frame it replaces its predecessor and the header keeps the mapped position throughout. A browser that rejects the assignment keeps the previous behaviour rather than losing the freeze.
Internal tooling and repository hygiene, with one adopter-visible behavior change: the no-comments scope config now refuses to load a hash family whose declared extensions map to a dialect with no measured lexer reference, instead of silently gating verdicts (and
--writestrip ranges) on a lexer nothing checks. No configuration in this repo or its mirror trips it (no-comments.config.jsoncdeclares onlyshellandyaml, both referenced), but a fork that declared a.pyhash family goes from loading to a hard refusal with no deprecation window.