feat: account-level Chat setting (user_settings), out of PostHog
Merged
HA
2d ago+17835+17835+01234567890123456789012345678901234567890123456789-397-397-012345678901234567890123456789
Stacked on #9155 (user_settings table); merge that first and let it deploy. Supersedes #9154.
Summary
- Chat leaves PostHog. Desktop and mobile get Settings → Experimental → Chat, stored per account in
user_settings.experimental_chatand off by default. Turn it on once and it applies on every device and on the server. - One rule everywhere: new sessions open as chat only when the setting is on and the agent supports ACP (Claude, Codex, OpenCode, Pi). Every other case opens a terminal. This applies to desktop, mobile, automations (the owner's setting) and cloud workspace starts.
- Existing chats always show. Chats from automations, another device, or an older build are adopted and listed whatever the setting says; the setting only decides how new sessions open.
- Presets bar is back on chat tabs, and "Fork into chat" is hidden while Chat is off.
How It Works
| Piece | What |
|---|---|
userSettings.get / userSettings.update | New router, mirroring channelNotificationPrefs. No row means the defaults; update upserts only the fields it was given and returns the settings |
lib/user-settings.ts | loadUserSettings / acpChatEnabled(userId). Used by automations (relay and cloud) and cloudWorkspace.create |
| Desktop | useAcpChatEnabled() is a cloudTrpc.userSettings.get query. AcpChatSetting writes the result back with setData. The old local acp-chat store is deleted and its key moved to DEAD_KEYS |
| Mobile | useAcpChatEnabled() is a React Query userSettings.get (refetched on foreground through the existing focus manager); the Settings switch writes the result into the cache |
Show vs open
- Showing no longer depends on the setting: desktop
useAutoAdoptChatSessions,useConsumeAutomationRunLink,seedPendingCloudChat; mobile home list, "Manage sessions", chat tabs and stop button, ended chats. - Opening new sessions checks the setting and ACP support: desktop launcher, presets, new chat (button and hotkey), new workspace, fork into chat; mobile new-session sheet and new terminal workspace.
Auth and the session are untouched.
Compatibility
- Old desktop and mobile builds still read the PostHog flag for what they show. Chat is off by default, so automations and cloud starts open terminals, which every build shows.
- Edge case: someone who turns Chat on in a new build but also uses an old build outside the flag won't see chats there until they update.
- tRPC: only additions (a new
userSettingsrouter). Nothing released loses an input or output. - Behaviour change: people in
acp-chatlose chat (and their automations switch to terminals) until they turn the setting on. An automation that continues a previous chat starts a fresh terminal while its owner has Chat off (hostSession.test.ts:156).
Manual QA
Earlier iteration (#9154, device-local setting), desktop dev app over CDP, before (main) vs after:
| Before | After | |
|---|---|---|
| Settings → Experimental | No Chat switch | Chat switch, off |
| Chat off: Claude preset | Terminal | Terminal |
| Chat off: terminal "CLI mode" menu | Offers Fork into chat | Fork session / Continue only |
| Chat on: Claude preset | n/a | Chat pane |
| Chat on: Copilot preset (no ACP) | n/a | Terminal |
| Presets bar on a chat tab | Hidden | Shown |
- Not re-run against this version. The table isn't on the workspace's Neon dev branch yet (applying migrations was blocked here). Re-check the rows above, plus: toggle on desktop, mobile follows after a foreground refetch
- Not run: mobile on a simulator; a real automation run end to end
Testing
tsc:packages/trpc,packages/auth,apps/desktopclean.apps/mobile: same 188 errors asmain, none in changed filesbun test: trpc 817 (newuser-settings.test.ts), desktop full suite (useCreateAgentSessionfixture now mocksuseAcpChatEnabled), mobile 248, auth 90bun run check:i18nclean (2 new strings translated in all 16 locales);biome checkclean- An independent review agent read the account-column version: no correctness bugs; its compatibility and session-cache findings are resolved by moving to
user_settingsand having automations follow the owner's setting
Follow-ups
- Delete the
acp-chatPostHog flag once no live build reads it - Settings copy says "Claude and Codex"; OpenCode and Pi also chat (re-keys 2 strings)
Summary by cubic
Moves the Chat experimental setting out of PostHog and per-device storage into an account-level user_settings row, so desktop, mobile, and server-side automations all agree on one value that's off by default.
- Adds a
userSettingstRPC router (get/update) that reads and upsertsexperimentalChat; no row means off. - Desktop and mobile replace their
acp-chatPostHog flag and local store withuserSettings.getqueries and persist viauserSettings.update. - Launch paths wait for the setting (
useResolveAcpChatEnabled) instead of reading "off" while it loads; desktop sign-out resets the query so the next account never sees the previous one's value. - Existing chats are always shown regardless of the setting; the setting only decides whether new sessions open as chat (and only for agents that support ACP).
- Automations open as chat only when the owner's setting is on; the old
acp-chatPostHog flag remains only for released builds and can be removed later. - The Chat switch is disabled while a save is in flight; "Track file changes per turn" stays visible but disabled while Chat is off.
- The desktop device-local
acp-chatstore is deleted and its key moves toDEAD_KEYS, so anyone with the setting enabled in a previous build will see terminals until they flip the new account-level toggle.
Migration
Existing acp-chat feature-flag users lose chat access until they enable the new setting in Settings → Experimental → Chat; the old local key is dead.