Code Mode: in-chat toggle, settings tab, and permission/command UX (#572)

* feat: add in-chat code mode toggle with claude/codex swap

* feat: show agent and add swap-and-retry on acpx permission card

* style: reorder permission card buttons (approve, deny, swap)

* feat: add tooltips to composer plus and web search buttons

* feat: add code mode settings tab with agent install/auth checks

* feat: show sign-in command when agent installed but signed out

* style: refine code-mode permission and command block UX

- Render permission block before the command block
- Collapse permission details after a response; click header to expand
- Drop status icons/badge; use minimal green / bold red blocks
- Auto-collapse the running command block once it completes

* feat: rotating progress labels for code-mode commands; darker tool borders

- Code-mode (acpx) command block shows status-aware labels: rotating
  'Working on the task…' phrases (5s each, holding on the last) while
  running, then 'Completed the task' / "Couldn't complete the task"
- Darken outer border on all tool blocks in light and dark modes

* fix: detect Claude Code sign-in via macOS Keychain

On macOS, Claude Code stores OAuth credentials in the login Keychain
(service 'Claude Code-credentials'), not in ~/.claude/.credentials.json.
Read the Keychain as a fallback so signed-in Mac users are detected.

* feat: persistent per-chat sessions for code-mode coding agents

- Use a named acpx session (rowboat-<runId>) per chat so follow-up
  coding requests resume the same agent and keep context
- Create the session once at chat start (sessions new --name), then
  prompt with -s <name>; reuse on follow-ups (no re-create)
- Drop the redundant in-chat 'reply yes' confirmation (the executeCommand
  permission card is the confirmation)
- Code-mode output uses plain-text paths (overrides global filepath rule)
- On not-installed/auth errors, point user to Settings -> Code Mode

* fix: code-mode session creation uses idempotent ensure, run sequentially

- Use 'sessions ensure --name' instead of 'sessions new' so reopening a
  chat resumes the existing session instead of erroring on a name clash
- Create the session and run the prompt as separate sequential calls so
  the permission/command blocks render one at a time (not all at once)

* fix: reliable Claude Code session resume on Windows (avoid claude.cmd EINVAL)

Resuming a code-mode chat after restarting the app spawns a fresh ACP
agent. On Windows + Node >=20.12 the bridge spawning claude.cmd throws
EINVAL, so the session queue owner fails to start. Rowboat injects
CLAUDE_CODE_EXECUTABLE=claude.exe to dodge this, but the override didn't
reliably reach the spawn. Windows-only; no-op on macOS/Linux.

- executeCommand now accepts an env override and the non-abortable
  fallback path passes it through (was silently dropped)
- resolveClaudeExeOnWindows also scans known npm/pnpm/volta global bin
  dirs, not just PATH (Electron's runtime PATH can omit them)
- add --timeout 600 to acpx prompt commands so a genuine stall fails
  cleanly instead of hanging on 'Running' forever
This commit is contained in:
gagan 2026-05-28 14:52:09 +05:30 committed by GitHub
parent b89b91258e
commit 537b6f66bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 1084 additions and 171 deletions

View file

@ -228,6 +228,7 @@ const ipcSchemas = {
voiceInput: z.boolean().optional(),
voiceOutput: z.enum(['summary', 'full']).optional(),
searchEnabled: z.boolean().optional(),
codeMode: z.enum(['claude', 'codex']).optional(),
middlePaneContext: z.discriminatedUnion('kind', [
z.object({
kind: z.literal('note'),
@ -424,6 +425,27 @@ const ipcSchemas = {
enabled: z.boolean(),
}),
},
'codeMode:getConfig': {
req: z.null(),
res: z.object({
enabled: z.boolean(),
}),
},
'codeMode:setConfig': {
req: z.object({
enabled: z.boolean(),
}),
res: z.object({
success: z.literal(true),
}),
},
'codeMode:checkAgentStatus': {
req: z.null(),
res: z.object({
claude: z.object({ installed: z.boolean(), signedIn: z.boolean() }),
codex: z.object({ installed: z.boolean(), signedIn: z.boolean() }),
}),
},
'granola:setConfig': {
req: z.object({
enabled: z.boolean(),

View file

@ -75,6 +75,7 @@ export const AskHumanRequestEvent = BaseRunEvent.extend({
type: z.literal("ask-human-request"),
toolCallId: z.string(),
query: z.string(),
options: z.array(z.string()).optional(),
});
export const AskHumanResponseEvent = BaseRunEvent.extend({