Repository navigation
v2.6.2 — sync recovery UX, callout fix, protocol versioning #113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
14ff77c
fix: don't offer restore when a conflict copy already preserves the note
ddutchie 07aec36
fix: re-enable inline callout widget with synchronous height measurement
ddutchie faed1c5
feat: sync protocol versioning + stale-peer detection
ddutchie 2eeacee
test: behavior suite for agent approval, plan and connector cards
ddutchie 128b9d6
fix: platform-neutral wording in agent tool-approval scope copy
ddutchie b878319
fix: stop capping chat/agent output tokens, breaking thinking models
ddutchie 4eb9549
fix: address PR review — stale-peer wording, styling, refresh, empty-…
ddutchie 288bae1
fix: address PR review — cache agent maxTokens, harden token-limit pa…
ddutchie File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| ## What's new | ||
|
|
||
| ### Fixes | ||
| - **Cairn now warns when another device is on an older sync version.** Device Sync records the sync-protocol version each of your devices is using. When one of them reports a *lower* version than this device, Settings → Sync (and the mobile Sync screen) show a "device needs updating" hint — because an older version may not honour deletions, so a note you delete can reappear from it. Devices on the same version see nothing new. | ||
| - **Callouts now render inline in the editor.** In the note editor's live preview, `> [!note]`-style callouts show as the same styled callout you see in reading mode, and click into raw Markdown to edit. This was implemented earlier but switched off because the cursor landed in the wrong place when you clicked near or below a callout — text after one was effectively unselectable. The callout now mounts with its real height before the editor measures the surrounding lines, so positions below it stay accurate and you can edit around a callout normally. | ||
| - **A note deleted on another device is no longer offered for restore twice.** When you delete a note on one device and edit that same note on another before it has seen the deletion, Device Sync keeps your edit as a conflict copy — and previously it *also* listed the note as "deleted on another device" with a Restore button. Restoring then left you with two identical notes to clean up. Cairn now recognises that your edit is already safe in the conflict copy and points you to the conflicts view to resolve it, instead of offering a redundant restore. Notes deleted with no competing edit still show Restore as before. | ||
| - **Agent tool approvals read correctly on Windows and Linux.** The confirmation shown before the agent runs a tool described where the action reaches as "this Mac" regardless of platform; it now says "this device." | ||
| - **Reasoning ("thinking") models no longer break the chat.** Cairn used to cap every reply at 4,096 output tokens. A reasoning model counts its private thinking against that budget, so on a hard question it could spend the whole cap thinking and stop before writing any answer — leaving an empty reply. Worse, sending your next message then failed outright with an "invalid request" error, because the empty turn was replayed to the provider. Cairn now lets models finish naturally by default (no artificial cap), so they think as long as they need and still answer. A new **Max output tokens** control in Settings → AI lets you set a deliberate cap if you want to bound cost per reply; leave it on **Auto** (the default) for the fix. As a safety net, if a reply ever is cut off mid-thought, Cairn shows the captured reasoning instead of a blank message, and a stalled empty turn can no longer poison your next message. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
| import { isSendableMessage } from "./llm"; | ||
|
|
||
| describe("isSendableMessage", () => { | ||
| // Regression: a "thinking" model that times out or stops streaming | ||
| // mid-reasoning persists an assistant turn whose only payload was reasoning | ||
| // (stripped before re-send). Replaying it makes OpenAI-compatible providers | ||
| // reject the whole request: "content or tool_calls must be set" (400). | ||
| it("drops an assistant turn with neither content nor tool_calls", () => { | ||
| expect(isSendableMessage({ role: "assistant", content: null })).toBe(false); | ||
| expect(isSendableMessage({ role: "assistant", content: "" })).toBe(false); | ||
| expect(isSendableMessage({ role: "assistant", content: " " })).toBe(false); | ||
| expect(isSendableMessage({ role: "assistant", content: "", tool_calls: [] })).toBe(false); | ||
| }); | ||
|
|
||
| it("keeps an assistant turn that has content", () => { | ||
| expect(isSendableMessage({ role: "assistant", content: "here is the draft" })).toBe(true); | ||
| }); | ||
|
|
||
| it("keeps an assistant turn that has tool_calls even with empty content", () => { | ||
| expect( | ||
| isSendableMessage({ role: "assistant", content: null, tool_calls: [{ id: "c1" }] }), | ||
| ).toBe(true); | ||
| }); | ||
|
|
||
| it("never filters non-assistant roles, even when empty", () => { | ||
| // user/system/tool turns with empty content are the caller's concern; the | ||
| // provider only rejects assistant turns for the missing-payload reason. | ||
| expect(isSendableMessage({ role: "user", content: "" })).toBe(true); | ||
| expect(isSendableMessage({ role: "system", content: null })).toBe(true); | ||
| expect(isSendableMessage({ role: "tool", content: "" })).toBe(true); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.