Repository navigation
Mobile chat tooling pass: expanded tools, reliable navigation, and token/context optimization - #82
Conversation
…letes Mobile chat went from 12 -> 19 Cairn tools, closing gaps vs desktop. New tools (mobile/src/chat/tools.ts): - get_task — full card detail (backed by existing getCard) - update_task — edit title/desc/priority/due/assignee AND move column (existing updateTask + moveCardToColumn) - rename_note — rename + rewrite inbound [[wikilinks]] in other notes, same-project title-collision guard, transactional (new renameNote query) - bulk_move_notes — move notes to a folder, returns count (new moveNotesToFolder) - list_folders — expose existing listFolders - delete_note / delete_task — soft-delete synced as tombstone (existing softDeleteNote + new deleteCard) New queries: renameNote, moveNotesToFolder, deleteCard. Chat tool chips now navigate for rename_note/get_task/update_task (open by id); deletes intentionally not tappable (item is gone). Desktop PI Agent allowlist left unchanged (destructive tools stay excluded for safety); its get_task/update_task were already present. mobile type-check + eslint clean; type-check:all clean; shared vitest 122. Updated AI Tooling Matrix note + changelog v0.1.3.
…s carry selection) A live tool-selection experiment (Rork exposed as OpenAI, claude-sonnet-4-6) showed the per-subsystem prompt guidance largely restated what each tool's description already tells the model — trimming it kept tool-selection accuracy equal or better. - Desktop buildSystemPrompt: ~960 -> ~239 tok (-75%). Kept only cross-cutting rules not expressible in a tool description (get_active_context/never-invent-id, write-then-confirm, suggest_connections UI directive, markdown/mermaid rendering); dropped the Notes/Tasks/Dashboards/Idea Flow/KG tutorials. - Mobile systemMessage: ~224 -> ~146 tok (-35%). Kept get_cairn_context-first, never-invent-id, [[wikilink]] output, write-confirm. Per-tool nuances (dashboard constants, idea-flow inline note/task creation) were already in the tool descriptions — no schema edits needed. New electron/lib/prompt-optimization.test.ts is an opt-in regression guard: it compares the old verbose prompt vs the current production prompt over realistic requests and asserts prod selects tools at least as well. No hardcoded secrets — reads PROMPT_TEST_BASE_URL / TEST_LLM_BASE_URL and skips cleanly (fast) when no endpoint is reachable. Mobile tool descriptions are parsed from tools.ts so they stay in sync without importing RN modules. Live result (claude-sonnet-4-6): Desktop PROD 10/10 vs OLD 9/10; Mobile PROD 9/10 == OLD 9/10. type-check:all + mobile type-check + eslint clean. Findings recorded in note "AI Agent Identity & System Prompts". Changelogs: v2.4.8, mobile v0.1.3.
…ness, multi-turn) Opt-in live test measuring whether the ~5,092-tok tool payload can be shrunk without hurting the model. A token breakdown showed the weight is in PARAMETER schemas (~3,308 tok), not descriptions — much of it mechanical JSON-Schema noise (giant ISO `pattern` regexes, `maximum: 9e15` bounds, `default`, `additionalProperties:false`) that carries no signal. Compares two compression levels vs full, scoring BOTH dimensions schemas drive: tool selection AND argument correctness. 12 single-turn scenarios (multi-field / enum / date / nested-array / id-array) plus 3 multi-turn sequences where later args depend on earlier tool results (id-threading). Also checks the transform against the mobile hand-written tool shape. Results (claude-sonnet-4-6, 3 runs): safe -10% never regressed and even fixes the pathological updatedAfter regex; aggressive -25% held selection but occasionally dipped one multi-turn arg. Mobile schemas are already lean (bloat is desktop zod->JSON-Schema only). Findings + decision in note "Tool Schema Token Optimization". No hardcoded secrets — reads PROMPT_TEST_BASE_URL / TEST_LLM_BASE_URL, skips cleanly (fast) when unreachable. eslint clean.
…ssion) Adds stripSchemaNoise() to schemaToParameters (electron/lib/tools.ts): removes JSON-Schema keywords the model never uses — `pattern` (e.g. the ~230-tok ISO-8601 regex on datetime fields), `minimum`/`maximum` bounds (incl. the 9e15 integer ceiling), `default`, and `additionalProperties:false`. Field descriptions are KEPT (they drive argument correctness). Server-side zod validation is unchanged. Chat tool payload: ~5,092 -> ~4,628 tok/request (-9%, ~464 tok every turn), with no change to tool selection or argument correctness — validated by the tool-schema experiment across 12 single-turn + 3 multi-turn (id-threading) scenarios. Scope is the Chat path (`TOOLS`); the MCP server passes schema.shape to the MCP SDK which does its own conversion, so MCP is unaffected. The experiment test now builds the true-full baseline from raw TOOL_SCHEMAS (since production TOOLS is already stripped) and its assertions gained a ±1 nondeterminism slack so a single flipped scenario per run doesn't false-fail. type-check:all clean; prompt-optimization + tool-schema regression tests green; 99 chat/tools unit tests pass. Note "Tool Schema Token Optimization" + changelog v2.4.8 updated.
…100k) gpt-tokenizer's default export is the o200k_base encoder (gpt-4o), not cl100k_base as the header claimed — verified empirically. Comment + describe label only; no behaviour change. Keeps the audit consistent with the o200k figures used in the prompt/tool-schema optimization notes. The audit's AFTER values already reflect this session's prompt trim + schema strip (it reads buildSystemPrompt/TOOLS live), so no numeric changes were needed.
Tool responses (JSON.stringify(result)) accrue in the conversation and are re-sent every subsequent turn, so one fat result permanently inflates context — the cause of "a single query jumps context 10-20%" (and on the 4K on-device window, outright overflow). Audited with electron/lib/tool-response-audit.test.ts. Offenders + fixes: - get_project_context_pack embedded up to 1000 chars per pinned note with NO cap on count, plus every open task's description → a busy project = ~5,788 tok in one call. Now: 5 most-recent pinned notes @ 300-char excerpt (+pinnedNotesTotal), open tasks capped to 20 total @ 120-char preview (+openTaskCount on mobile). Applied to BOTH mobile (db/queries.ts) and desktop (electron/shared/ read-tools-pure.ts, used by MCP + desktop chat). Busy pack 5,788 -> 1,735 tok (-70%); no longer overflows the on-device window. - get_note returned full content untruncated (~2,706 tok for a long doc). Mobile get_note now returns full content for small notes (<=1500 chars) but a line-numbered OUTLINE + intro for long ones (mode:"outline"), and a new get_note_range(id, start_line, end_line?) tool reads just the needed slice. Long-note read 2,706 -> 315 tok (-88%). full=true forces whole content. The getNote query + NoteDetailScreen UI are untouched (still full content). Shared: shared/notes/toc.ts gains buildNoteOutline() + sliceLines() (+7 tests). type-check:all + mobile type-check + eslint clean; shared 129; mcp-server + chat-executor 83; payload-baseline + context-audit green. Note "Tool Response Size Optimization" + changelogs v2.4.8 / mobile v0.1.3.
Follow-up to the response-size work. A pinned note in get_project_context_pack
is now represented by its heading OUTLINE when it has section headings (≥1 h2) —
a compact, structured semantic summary — falling back to a short excerpt only
for flat/heading-less notes. Applied to both mobile (db/queries.ts) and desktop
(electron/shared/read-tools-pure.ts → MCP + desktop chat).
Rationale: embeddings are one-way/lossy and can't be decoded to text, so they
can't replace an excerpt; but human-authored headings already ARE a summary. For
a structured note the outline conveys the whole note's shape
(Auth Design › Token rotation › Refresh flow) in ~half the tokens. Measured: 5
structured pinned notes 403 → 157 tok (-61%) vs the excerpt shape.
New shared noteDigest() in shared/notes/toc.ts (+tests). Desktop read-tools-pure
imports repo-root shared via relative path (no @cairn/shared alias on desktop);
MCP runtime bundle rebuilds clean. Updated the chat-executor pack test to the new
{outline|excerpt} digest shape (structured→outline, flat→excerpt).
type-check:all + mobile + eslint clean; shared 141; mcp-server + chat-executor 83.
Note "Tool Response Size Optimization" + changelogs v2.4.8 / mobile v0.1.3.
Audits whether tool error responses let the model recover on its own (re-fetch a
valid id via a read/search tool) vs dead-end or blindly retry. Drives a live
conversation with a STALE id injected, then scores whether the model recovers
and whether it does so immediately (no wasted retry turn). Compares the current
terse error text vs an actionable variant that names the bad id + recovery tool.
Finding (claude-sonnet-4-6, stable): both recover 3/3, immediate 2/3 — a capable
model self-corrects from "Not found" alone, so error text quality barely moves
the needle here. No production change shipped; the actionable-error rewrite is
parked as a low-cost win to validate against weaker/on-device models (where
terse errors are more likely to dead-end). Rationale + error inventory in note
"Tool Error Self-Correction Audit".
Plumbing verified sound: {error} results reach the model on all surfaces
(mobile output-available part, desktop MCP isError, desktop chat). Opt-in, no
secrets; skips cleanly with no endpoint. eslint clean.
… keyboard 1. Wikilinks by id (collision-proof): the chat agent now links notes/tasks as [[id]] using the exact id, which the MarkdownView resolver renders as the note's canonical title — so a link can never open the wrong item when two share a title. [[Title]] still works as a fallback, and findNoteIdByTitle/ findCardIdByTitle are now deterministic (ORDER BY updated_at DESC) instead of an arbitrary LIMIT 1. New liveNoteTitleById/liveCardTitleById reverse lookups; prompts updated to prefer [[id]]. 2. Attach button centring: the image-attachment GlassMenu (a native Host) drifted up under the composer row's flex-end alignment when the keyboard opened or the input grew. Wrapped it in a fixed 36px RN slot so it stays aligned with the send button regardless of keyboard/multiline state. 3. Stuck keyboard: send() now dismisses via KeyboardController.dismiss() (the keyboard-controller API — RN's Keyboard.dismiss desyncs with this library and left it stuck until an app switch), and the screen also dismisses on blur. type-check:all + mobile type-check + eslint clean; shared markdown tests pass. Changelog v0.1.3.
|
Warning Review limit reached
Next review available in: 12 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (13)
📝 WalkthroughWalkthroughThe changes compact AI prompts, tool schemas, and project context responses; add outline-based note reads and mobile note/task tools; improve id-based link resolution and chat behavior; and add live and deterministic audits for selection, recovery, and token usage. ChangesAI context and mobile chat
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Chat
participant MobileTools
participant Database
participant MarkdownView
Chat->>MobileTools: invoke note or task tool
MobileTools->>Database: read or mutate entity
Database-->>MobileTools: return result and id
MobileTools-->>Chat: return tool response
Chat->>MarkdownView: render entity reference
MarkdownView->>Database: resolve id to live title
Database-->>MarkdownView: return title
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (2)
shared/notes/toc.ts (1)
27-44: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider extracting shared fence-detection and heading-match logic.
extractHeadings(L27-44) andbuildNoteOutline(L64-79) duplicate the sameinFencetoggle,line.startsWith("```")check, and^(#{1,3})\s+(.+)regex. If one is fixed for an edge case (e.g., tilde fences, indented fences), the other can silently diverge. The agreement test attoc.test.ts:32-36guards heading texts but not structural details like line numbers or ids.Consider extracting a shared
matchHeadingLine(line, inFence)helper or ascanHeadings(markdown)that both functions build on.Also applies to: 64-79
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shared/notes/toc.ts` around lines 27 - 44, Extract the duplicated fence detection and heading matching from extractHeadings and buildNoteOutline into a shared helper such as matchHeadingLine or scanHeadings. Update both functions to use the shared logic so fence handling and the ^(#{1,3}) heading pattern remain consistent, while preserving their existing heading text, line number, and id behavior.electron/lib/prompt-optimization.test.ts (1)
202-219: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
mobileTools()is duplicated across two test files with anadditionalPropertiesinconsistency. Both files define the same regex-basedmobileTools()function to parse tool names/descriptions frommobile/src/chat/tools.ts, butprompt-optimization.test.tsusesadditionalProperties: truewhiletool-schema-optimization.test.tsusesadditionalProperties: false. Thefalsevalue matches the mobileobj()helper. Extract this to a shared helper module (e.g.,electron/lib/test-helpers.ts) to eliminate duplication, alignadditionalPropertiestofalse, and make the regex easier to maintain in one place.
electron/lib/prompt-optimization.test.ts#L202-L219: extractmobileTools()to a shared module; changeadditionalProperties: true→false.electron/lib/tool-schema-optimization.test.ts#L221-L237: replace inlinemobileTools()with import from the shared module.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@electron/lib/prompt-optimization.test.ts` around lines 202 - 219, Extract the duplicated mobileTools() parser from electron/lib/prompt-optimization.test.ts#L202-L219 into a shared helper module, set its generated schema to additionalProperties: false, and export it. Replace the inline mobileTools() implementation in electron/lib/tool-schema-optimization.test.ts#L221-L237 with an import from that helper; both sites should use the shared regex and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@electron/lib/prompt-optimization.test.ts`:
- Around line 108-122: Add a 30-second AbortSignal timeout to the LLM fetch
calls in taskToolCall (electron/lib/prompt-optimization.test.ts:108-122),
runWithInjectedError (electron/lib/tool-error-audit.test.ts:69-73), taskToolCall
(electron/lib/tool-schema-optimization.test.ts:153-157), and runConversation
(electron/lib/tool-schema-optimization.test.ts:194-198).
- Around line 202-219: Update mobileTools() to use the same robust tool-schema
extraction approach as tool-schema-optimization.test.ts, supporting the mobile
source’s current description formats without silently omitting tools when
formatting changes. Align the generated parameters schema with the mobile obj()
helper by setting additionalProperties to false, and reuse the established
implementation rather than maintaining divergent parsing logic.
- Around line 190-196: Update the fourth instruction in PROD_MOBILE to match the
exact note-title linking wording used by mobile/src/chat/agent.ts
systemMessage(), replacing the stale [[double brackets]] text. Keep the
remaining prompt instructions unchanged and ensure the test fixture stays
synchronized with the production mobile prompt.
In `@electron/lib/tool-schema-optimization.test.ts`:
- Around line 221-237: Extract the shared mobile tool-schema parsing logic from
mobileTools() into a reusable helper, preserving additionalProperties: false to
match the mobile obj() helper. Update both tool optimization tests to use the
helper and remove the duplicated implementations, while keeping the differing
schema behavior of non-mobile tools unchanged.
In `@mobile/app/`(tabs)/chat/index.tsx:
- Around line 410-425: Update the attachSlot style used by the wrapper around
GlassMenu to set alignSelf: "center", matching sendBtn’s alignment so the attach
control remains vertically aligned as the multiline input grows; keep its fixed
height and existing layout behavior unchanged.
In `@mobile/src/db/queries.ts`:
- Around line 1021-1034: Update moveNotesToFolder so its UPDATE only matches
live note records by adding the existing LIVE/type='note' predicates alongside
the id filter. Preserve the current folder, timestamp, version, notification,
and affected-row behavior for matching notes.
---
Nitpick comments:
In `@electron/lib/prompt-optimization.test.ts`:
- Around line 202-219: Extract the duplicated mobileTools() parser from
electron/lib/prompt-optimization.test.ts#L202-L219 into a shared helper module,
set its generated schema to additionalProperties: false, and export it. Replace
the inline mobileTools() implementation in
electron/lib/tool-schema-optimization.test.ts#L221-L237 with an import from that
helper; both sites should use the shared regex and behavior.
In `@shared/notes/toc.ts`:
- Around line 27-44: Extract the duplicated fence detection and heading matching
from extractHeadings and buildNoteOutline into a shared helper such as
matchHeadingLine or scanHeadings. Update both functions to use the shared logic
so fence handling and the ^(#{1,3}) heading pattern remain consistent, while
preserving their existing heading text, line number, and id behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: a3c852ff-9d5e-4e70-a7cb-b3954896c545
📒 Files selected for processing (19)
README.mdchangelogs/v2.4.8.mdelectron/ipc/chat-executor.test.tselectron/lib/context-audit.test.tselectron/lib/prompt-optimization.test.tselectron/lib/tool-error-audit.test.tselectron/lib/tool-response-audit.test.tselectron/lib/tool-schema-optimization.test.tselectron/lib/tools.tselectron/shared/read-tools-pure.tsmobile/app/(tabs)/chat/index.tsxmobile/changelogs/v0.1.3.mdmobile/src/chat/agent.tsmobile/src/chat/providers/apple.tsmobile/src/chat/tools.tsmobile/src/components/MarkdownView.tsxmobile/src/db/queries.tsshared/notes/toc.test.tsshared/notes/toc.ts
| const res = await fetch(`${BASE_URL}/chat/completions`, { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| ...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}), | ||
| }, | ||
| body: JSON.stringify({ | ||
| model: MODEL, | ||
| messages, | ||
| tools, | ||
| tool_choice: "auto", | ||
| temperature: 0.2, | ||
| stream: false, | ||
| }), | ||
| }); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
LLM fetch calls lack timeouts across three audit test files. All three live test files check endpoint reachability with AbortSignal.timeout(2500) in endpointUp(), but the actual /chat/completions fetch calls have no timeout — if the endpoint accepts the connection but hangs during generation, the test blocks until the 180–240s vitest timeout. Add signal: AbortSignal.timeout(30_000) to each LLM fetch call.
electron/lib/prompt-optimization.test.ts#L108-L122: addsignal: AbortSignal.timeout(30_000)to the fetch intaskToolCall.electron/lib/tool-error-audit.test.ts#L69-L73: addsignal: AbortSignal.timeout(30_000)to the fetch inrunWithInjectedError.electron/lib/tool-schema-optimization.test.ts#L153-L157: addsignal: AbortSignal.timeout(30_000)to the fetch intaskToolCall.electron/lib/tool-schema-optimization.test.ts#L194-L198: addsignal: AbortSignal.timeout(30_000)to the fetch inrunConversation.
📍 Affects 3 files
electron/lib/prompt-optimization.test.ts#L108-L122(this comment)electron/lib/tool-error-audit.test.ts#L69-L73electron/lib/tool-schema-optimization.test.ts#L153-L157electron/lib/tool-schema-optimization.test.ts#L194-L198
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@electron/lib/prompt-optimization.test.ts` around lines 108 - 122, Add a
30-second AbortSignal timeout to the LLM fetch calls in taskToolCall
(electron/lib/prompt-optimization.test.ts:108-122), runWithInjectedError
(electron/lib/tool-error-audit.test.ts:69-73), taskToolCall
(electron/lib/tool-schema-optimization.test.ts:153-157), and runConversation
(electron/lib/tool-schema-optimization.test.ts:194-198).
Valid findings fixed: - Add AbortSignal.timeout(30_000) to all 4 live LLM fetch calls (prompt-optimization taskToolCall, tool-error-audit runWithInjectedError, tool-schema-optimization taskToolCall + runConversation) so a hung endpoint can't hang the run. - Extract the duplicated mobile-tools parser into a shared electron/lib/ mobile-tools-fixture.ts (parseMobileTools): tolerant regex ([gs], relaxed whitespace) that handles single- and multi-line description formats and throws if it parses nothing (guards against silent omission on a format change); emits additionalProperties:false to match mobile's obj() helper. Both AI tests now import it instead of maintaining divergent parsers. - Sync PROD_MOBILE fixture's note-linking instruction to the current mobile/src/chat/agent.ts systemMessage() ([[id]] wording; the stale [[double brackets]] text was left behind). - attachSlot: add alignSelf:"center" to match sendBtn so the attach control stays vertically aligned as the multiline input grows. - moveNotesToFolder: scope the UPDATE to live notes (LIVE + type='note') alongside the id filter, so a tombstoned/non-note row can't be moved. - shared/notes/toc.ts: extract shared scanHeadings() so extractHeadings and buildNoteOutline share one fence-tracking + heading-match implementation. Skipped: none — all findings were still valid against current code. type-check:all + mobile type-check + eslint clean; shared toc/markdown 25, chat-executor/mcp-server/shared/context-audit 88, AI experiment tests skip cleanly (token asserts pass). Fixture parses all 19 mobile tools.
The image-attachment button was mis-centred vertically until a tab change forced a re-layout. Cause: the @expo/ui SwiftUI Host uses matchContents, which measures its content asynchronously after first mount, so on initial paint the trigger wasn't centred in its slot. Fix: pin the Host to an explicit 32x32 frame (attachContainer) and, in GlassMenu, skip matchContents when the container has a fixed width+height — a concrete frame lays out correctly on first paint with no async remeasure. Behaviour is unchanged for other GlassMenu callers (they pass no fixed size, so matchContents stays on). mobile type-check + eslint clean. Changelog v0.1.3.
…t mount Context ring: the context-window usage ring was session-only state, lost when the Chat tab unmounted. Persist the latest usage in app_settings (local-only, no capture trigger) — saveLastChatUsage on each final-usage event, seed the state from loadLastChatUsage() on mount, and clear it in clearChatHistory. The ring now restores on reopen instead of vanishing until the next turn. Attach icon: the @expo/ui SwiftUI Host measures its content asynchronously after mount, so the trigger rendered mis-centred until a later re-layout (keyboard/tab change) forced a remeasure. GlassMenu now bumps state once on the Host's onLayoutContent (re-keying the Menu) to force a single re-layout so it settles correctly on first paint; the attach container keeps a 32x32 size hint. mobile type-check + eslint + type-check:all clean. Changelog v0.1.3.
…lock The ScrollView's onContentSizeChange unconditionally called scrollToEnd, so any content-height change — including expanding a previous message's Reasoning block while scrolled up — yanked the transcript to the bottom. Track whether the user is near the bottom (onScroll, <80px from end) and only auto-follow on content growth when true, so streaming still pins to the bottom but manual expansions don't. Sending a message resets nearBottom=true so the reply is followed. mobile type-check + eslint clean. Changelog v0.1.3.
…n scroll Semantic search parity + no buried notes: - The chat semantic_search_notes tool now calls catchUpIndex() first (like the Search tab), so it indexes new/edited notes and searches a fresh index instead of a stale one — previously recent notes were missed and it never downloaded assets on a device that hadn't opened Search. - semanticSearch(ws, q, k?) now makes the k slice optional and returns the FULL hybrid-ranked candidate list when k is omitted. Both callers (tool + Search tab) now gather the full lists across workspaces, merge, re-rank by the hybrid `rank`, and slice ONCE — so a note ranked low within its workspace but high globally isn't dropped before the merge (the "correct note buried at #36 → promoted to #1" case). The tool also dropped a redundant re-round of the already-rounded display score. Keyboard-open scroll regression: the near-bottom auto-follow guard (added to stop reasoning-expand from jumping) also blocked scrolling to the latest when the keyboard opened. TextInput onFocus now sets nearBottom=true and scrolls to end, so focusing the composer follows to the newest message again. mobile type-check + eslint clean. Changelog v0.1.3.
What does this PR do?
A focused pass on the AI/agent layer plus three mobile chat fixes. It (1) expands the mobile chat tool set to full note/task CRUD, (2) makes chat→note/card navigation reliable and collision-proof, and (3) cuts the tokens spent per chat turn across three fronts — system prompts, tool schemas, and tool responses — each validated by an opt-in, live tool-selection/arg-correctness experiment (no secrets in source). Net effect: the assistant can do more, links land on the right item, and a single query no longer balloons the context window.
Type of change
Summary of changes
Mobile chat capability + fixes
get_task,update_task(incl. column move),rename_note(with wikilink fixups),bulk_move_notes,list_folders,delete_note,delete_task.[[wikilinks]]resolve to note and card ids, baked at preprocess time.[[id]](rendered as the note's title), so a link can't open the wrong item when two share a title;[[Title]]still works and title lookup is now deterministic.Token / context optimization (all measured, no capability loss)
buildSystemPrompt~960 → ~240 tok; mobilesystemMessage~224 → ~146 tok. Per-tool guidance moved to (already lived in) the tool descriptions.pattern/min/max/default/additionalProperties) from the Chat tool payload: ~5,092 → ~4,628 tok/turn. Server-side zod validation unchanged.get_project_context_pack(5 pinned notes / 20 tasks, short previews) and gaveget_notea TOC/outline mode for long notes + aget_note_rangefollow-up (busy pack 5,788 → 1,735 tok; long note 2,706 → 315). Pinned notes are now summarised by their heading outline when structured.Screenshots / recording
N/A for the desktop AI-layer changes. The three mobile fixes (attach-button centring, keyboard dismissal, wikilink navigation) are behavioural and best verified on device — see "Notes for reviewer".
Checklist
npm run type-check:allpassesnpm run lintpassesnpm testpasses — new opt-in live tests (electron/lib/{prompt-optimization,tool-schema-optimization,tool-response-audit,tool-error-audit}.test.ts) skip cleanly when no LLM endpoint is configured (no secrets in source); all other unit tests (shared, mcp-server, chat-executor, payload-baseline, context-audit, toc) passnpm run test:e2e— N/A (no desktop renderer UI change)theme/var()); no new desktop UItext-[Npx]pixel font classes — N/A (no changed desktop UI)handle()and returnIpcResult<T>— N/A (no new IPC handlers)schema.ts— N/A (no schema changes)electron/db/queries.ts— N/A on desktop (no new desktop SQL); new mobile queries live inmobile/src/db/queries.ts(mobile's own single source)dependencies/ license map — N/A (no new deps)mobile/src/chat/tools.ts). Desktopget_project_context_packresponse shape changed (capped) — covered by updatedchat-executortest.--external:<pkg>bundle-guard allowlist — N/A (no compile flag changes; MCP runtime bundle rebuilds clean)Notes for reviewer
PROMPT_TEST_BASE_URL/TEST_LLM_BASE_URL— they skip fast when unset, so CI is unaffected. Results were captured against a local Rork-as-OpenAI endpoint (claude-sonnet-4-6) and written up in the notes below.TOOLS); the MCP server passesschema.shapeto the MCP SDK (its own conversion), so MCP is unaffected — this is documented in the note.Summary by CodeRabbit