feat: configure AI chat system prompts#775
Conversation
WalkthroughAdds a normalized, persisted custom AI chat system prompt across core streaming, desktop settings, mobile settings, provider wiring, tests, and privacy documentation. ChangesCustom AI chat system prompt
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant SettingsUI
participant SettingsProvider
participant ChatProvider
participant streamChat
participant Model
SettingsUI->>SettingsProvider: save chatSystemPrompt
ChatProvider->>SettingsProvider: read latest setting
ChatProvider->>streamChat: pass customSystemPrompt
streamChat->>Model: append normalized prompt
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
packages/core/src/ai/chat/stream-chat.ts (1)
88-95: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winCover the
streamChatforwarding path.The custom-prompt assertion exercises
streamChatTurndirectly, so it would not catch a regression that omits the prompt fromchatSystemPromptduring context-window fitting or from the subsequentstreamChatTurncall. Add a focusedstreamChat-level test that captures the provider request and verifies the custom prompt is included.This is based on the PR objective that the prompt participates in context accounting and provider requests, plus the guideline to test the changed logic specifically.
🤖 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 `@packages/core/src/ai/chat/stream-chat.ts` around lines 88 - 95, The existing coverage only tests streamChatTurn directly; add a focused test for streamChat that captures the provider request and verifies customSystemPrompt is forwarded into both context-window accounting and the subsequent streamChatTurn request. Use the streamChat entry point and existing test/provider mocking utilities, preserving the current behavior for requests without a custom prompt.Source: Coding guidelines
apps/desktop/src/components/settings/ai-chat-section.tsx (1)
19-30: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winEvery keystroke persists the setting; mobile intentionally avoids this.
valueis bound directly tosettings.chatSystemPromptandonChangecallsupdateSettingson every keystroke (normalization only happens on blur). For a field allowing up to 20,000 characters, this fires a settings write on every character typed. Contrast with the mobileChatSystemPromptDrawer, which buffers edits in localdraftstate and only persists on explicit Save/"Use default" — precisely to avoid this. Consider mirroring that pattern here: keep a local draft synced fromsettings.chatSystemPromptwhen not dirty, and only callupdateSettingson blur (or Save).Beyond the write volume, if the underlying persistence is fire-and-forget without strict ordering, rapid typing could theoretically let an earlier in-flight write resolve after a later one and clobber the latest text. Worth confirming against the settings-provider implementation.
♻️ Suggested draft-buffering pattern (mirrors mobile drawer)
+import { useState } from 'react' import type { ReactElement } from 'react' import { CHAT_SYSTEM_PROMPT_MAX_LENGTH, normalizeChatSystemPrompt } from '`@reflect/core`' ... export function AiChatSection(): ReactElement { const { settings, updateSettings } = useSettings() + const [draft, setDraft] = useState(settings.chatSystemPrompt) + const [dirty, setDirty] = useState(false) + const currentDraft = dirty ? draft : settings.chatSystemPrompt return ( <SettingsSection id="ai-chat"> <SettingsField ...> <Textarea aria-label="System prompt" - value={settings.chatSystemPrompt} - onChange={(event) => updateSettings({ chatSystemPrompt: event.target.value })} - onBlur={(event) => - updateSettings({ chatSystemPrompt: normalizeChatSystemPrompt(event.target.value) }) - } + value={currentDraft} + onChange={(event) => { + setDirty(true) + setDraft(event.target.value) + }} + onBlur={() => + updateSettings({ chatSystemPrompt: normalizeChatSystemPrompt(currentDraft) }) + }🤖 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 `@apps/desktop/src/components/settings/ai-chat-section.tsx` around lines 19 - 30, Update the settings chat prompt editor around the Textarea to use a local draft instead of binding directly to settings.chatSystemPrompt. Keep the draft synchronized with external settings changes while the field is not dirty, update only the draft during typing, and persist the normalized draft on blur (or an explicit save) via updateSettings; preserve the existing max length and default 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 `@apps/desktop/src/providers/chat-provider.tsx`:
- Line 306: In the send flow, snapshot chatSystemPromptRef.current before the
first await, then pass that captured value as customSystemPrompt instead of
reading the ref after Promise.all. Keep the snapshot scoped to the submitted
turn so later setting changes do not affect the in-flight request.
In `@packages/core/src/settings/schema.test.ts`:
- Around line 123-126: Update the test around settingsSchema.parse and
chatSystemPrompt to assert that the truncated value equals the expected prefix
of the repeated input, while retaining the existing maximum-length assertion if
useful. Verify the result preserves the first CHAT_SYSTEM_PROMPT_MAX_LENGTH
characters rather than only checking its length.
---
Nitpick comments:
In `@apps/desktop/src/components/settings/ai-chat-section.tsx`:
- Around line 19-30: Update the settings chat prompt editor around the Textarea
to use a local draft instead of binding directly to settings.chatSystemPrompt.
Keep the draft synchronized with external settings changes while the field is
not dirty, update only the draft during typing, and persist the normalized draft
on blur (or an explicit save) via updateSettings; preserve the existing max
length and default behavior.
In `@packages/core/src/ai/chat/stream-chat.ts`:
- Around line 88-95: The existing coverage only tests streamChatTurn directly;
add a focused test for streamChat that captures the provider request and
verifies customSystemPrompt is forwarded into both context-window accounting and
the subsequent streamChatTurn request. Use the streamChat entry point and
existing test/provider mocking utilities, preserving the current behavior for
requests without a custom prompt.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a6b5013a-900c-42b0-b6ec-a9a47f45b689
📒 Files selected for processing (24)
apps/desktop/src/components/chat/chat-screen.test.tsxapps/desktop/src/components/daily-stream.test.tsxapps/desktop/src/components/route-content.test.tsxapps/desktop/src/components/settings-screen.test.tsxapps/desktop/src/components/settings-screen.tsxapps/desktop/src/components/settings/ai-chat-section.tsxapps/desktop/src/components/settings/sections.tsapps/desktop/src/mobile/chat-system-prompt-drawer.tsxapps/desktop/src/mobile/mobile-screen.test.tsxapps/desktop/src/mobile/note-conflict.test.tsxapps/desktop/src/mobile/screens/chat.test.tsxapps/desktop/src/mobile/screens/settings.test.tsxapps/desktop/src/mobile/screens/settings.tsxapps/desktop/src/providers/chat-provider.test.tsxapps/desktop/src/providers/chat-provider.tsxapps/desktop/src/providers/settings-provider.test.tsxdocs/privacy.mdpackages/core/src/ai/chat/stream-chat.test.tspackages/core/src/ai/chat/stream-chat.tspackages/core/src/ai/chat/system-prompt.test.tspackages/core/src/ai/chat/system-prompt.tspackages/core/src/exports/platform.tspackages/core/src/settings/schema.test.tspackages/core/src/settings/schema.ts
Problem
AI chat always used Reflect's fixed system prompt. Users could choose providers and save editor-selection prompts, but they had no way to configure the chat assistant's tone, format, or working style.
A user-defined prompt also cannot safely replace Reflect's built-in prompt: the built-in instructions describe note retrieval, citations, tool usage, and private-note handling.
Before -> After
Changes
Persist a bounded chat prompt
chatSystemPromptto the shared settings schema with an empty default.Apply the prompt to every chat request
chatSystemPromptwhile retaining Reflect's retrieval, citation, and private-note rules.Expose the setting across platforms
Document the data boundary
private: trueprotects tool-based note reads, not content a user manually pastes into a message or prompt.Tests
Verification
pnpm check— passed (typecheck, Oxlint, and React ESLint; two pre-existing max-lines warnings remain).pnpm build— passed (expected local Sentry credential and bundle-size warnings only).pnpm --filter @reflect/core exec vitest run src/settings/schema.test.ts src/ai/chat/system-prompt.test.ts src/ai/chat/stream-chat.test.ts— 38 tests passed.pnpm --filter @reflect/desktop exec vitest run src/providers/chat-provider.test.tsx src/providers/settings-provider.test.tsx src/components/settings-screen.test.tsx src/components/settings/settings-navigator.test.tsx src/mobile/screens/settings.test.tsx src/components/chat/chat-screen.test.tsx src/mobile/screens/chat.test.tsx— 114 tests passed.Risk / Rollout
Note
Medium Risk
Custom prompt text is sent to the user’s AI provider on every chat turn; built-in private-note tool gates remain, but pasted or prompt content is not protected.
Overview
Adds a device-local
chatSystemPromptsetting so users can supply extra instructions for every AI chat turn without replacing Reflect’s built-in grounding, tool, citation, and private-note rules.Core: New settings field (trimmed, capped at 20k chars, default empty).
chatSystemPromptis appended inchatSystemPrompt()after the built-in rules;streamChatpasses it through context-window fitting and the provider request.ChatProviderreads the current value at send time via a ref so changes apply on the next message in an open thread.UI: Desktop AI chat settings section (draft textarea, blur/unmount save, Use default). Mobile System prompt row and drawer (Save / Use default). Settings navigator registry updated.
Docs: Privacy guide notes the prompt is stored locally and sent to the user’s provider each turn, and that private-note protection does not cover pasted content or custom prompt text.
Test fixtures and suites extended for schema, streaming, provider wiring, and both settings surfaces.
Reviewed by Cursor Bugbot for commit 0891e89. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
New Features
Documentation
Tests