Skip to content

Add a Stop button over terminals while an agent turn runs - #17729

Closed
azooz2003-bit wants to merge 31 commits into
mainfrom
feat/agent-action-buttons
Closed

azooz2003-bit wants to merge 31 commits into
mainfrom
feat/agent-action-buttons

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

First slice of #15223 (clickable agent actions). With Settings > Terminal > Agent Stop Button on (agentActions.turnControl, off by default), a small Stop pill sits in a terminal pane's bottom-trailing corner while Claude Code or Codex is running a turn in that pane. Clicking it interrupts the turn.

  • When it shows: the pane's journaled lifecycle says claude_code or codex is running, the same state the TextBox checks before it forwards Escape. Other agents get no button until their interrupt key is verified. Toggling the setting mid-turn shows or hides the pill at once; it listens for defaults changes only while an agent is running.
  • What a click does: sends one Escape through TerminalPanel.sendNamedKeyResult, the named-key path the TextBox and mobile.chat.interrupt already use. The click is re-checked against the current lifecycle first, so a late click after the turn ended sends nothing.
  • Leaving running: Claude Code runs no Stop hook when a turn is interrupted (Sidebar 'Running' status stays on after user interrupts an agent turn #4389), so the pane would keep showing Running and the button would stay up. After sending Escape, cmux asks the agent journal to settle every Claude session its own fold has running on that surface: each gets a turnCompleted event (AgentJournalEventDraft.userInterrupt, native event cmux.user_interrupt) with no notification, so no "done" alert fires. Settling from the fold rather than the hook store means nested claude -p sessions and relayed sessions are handled correctly. If Claude keeps working, its next PreToolUse hook declares the pane running again. Codex is not settled this way: it journals lifecycle only at prompt submit and Stop, so a settle could leave a still-working Codex pane idle.
  • One Escape per click: Stop holds for 1.5 s after a click. A second Escape at Claude's idle prompt would open its rewind menu.
  • Known limit: a Claude background shell that outlives the interrupt is not counted as pending work, so the pane reads idle while it runs. Hibernation's process checks and idle interval still apply.
  • Where it lives: TerminalAgentTurnControlView, an AppKit overlay mounted in GhosttySurfaceScrollView beside the password-input badge and raised above the find overlay. Only the button takes clicks; the rest of the overlay passes hit-testing through to the terminal. Workspace and Dock lifecycle writes both refresh it.
  • Settings: a new agentActions section in the catalog, cmux.json ({"agentActions": {"turnControl": true}}), the schema, and settings search. Later categories from the RFC add one key each.

Testing

  • swift test --package-path Packages/macOS/CmuxAgentJournal --filter AgentJournalUserInterruptTests (local, 3 passed): turnStarted, interrupt, turnStarted gives running, idle with no notification identity, then running, for claude and codex; the fold's interrupt drafts cover only live running sessions on the surface (including a session without an id) and settle it to idle without touching another surface.
  • swift test --package-path Packages/macOS/CmuxSettings --filter SettingCatalogTests (local, passed): the key defaults off and matches its section prefix.
  • cmuxTests/TerminalAgentTurnControlTests.swift (added, runs in CI): the pill shows only while enabled and running, hides mid-turn when the setting turns off, stays hidden by default, two quick Stop clicks enqueue exactly one key event, and a click after the agent stopped enqueues none. A journal-center test checks that recordUserInterrupt journals the interrupt for the session the center has running on the surface.
  • python3 tests/test_cmux_settings_supported_paths.py (local, passed).
  • python3 scripts/verify-local.py: 15/15 selected checks passed (localization parity, xcstrings, schema, project and test wiring).
  • scripts/ui-test AgentActionPillUITests at 6c11b3e (run, both tests passed): with the setting on, journaling an agent.turn.started for a Claude Code session on a terminal over the socket shows the Stop pill; clicking it hides the pill, so the interrupt settled the turn. With the setting off, the same event shows no pill. The turn is journaled directly, so a live Claude process was not interrupted.

Changelog

Added: An optional Stop button over a terminal interrupts a running Claude Code or Codex turn (Settings > Terminal > Agent Stop Button).

Demo Video

Captured by cmuxUITests/AgentActionPillUITests in CI run 36425803310 at 6c11b3e (shell prompt blanked). Running, then after clicking Stop:

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added an optional Agent Stop Button setting. When enabled, a Stop button appears in the terminal while Claude Code or Codex is working; clicking it sends the agent’s interrupt key.
    • Added localized setting labels and configuration help across supported languages.

Migrated from #15234 after correcting the PR author identity. The head branch and commit history are preserved.


Summary by cubic

Adds an optional Stop button for interrupting running Claude Code or Codex turns, behind a new agentActions.turnControl setting (off by default, Settings > Terminal > Agent Stop Button). When enabled, a Stop pill appears in a terminal pane's bottom-trailing corner while a supported agent is running a turn there, and clicking it sends the agent's interrupt key.

Behavior

  • The pill shows only while the pane's journaled lifecycle says claude_code or codex is running; other agents get no button until their interrupt key is verified, and toggling the setting shows or hides it at once.
  • The click sends one Escape through the same named-key path the TextBox uses, re-checked against the current lifecycle so a late click sends nothing.
  • Claude Code runs no Stop hook on interrupt, so cmux journals a user-interrupt turn completion to return the pane to idle; Codex is not settled this way, since it would leave a still-working pane idle.
  • Stop holds for 1.5 s after a click so a second Escape at Claude's idle prompt doesn't open its rewind menu.

Settings

  • New agentActions catalog section with turnControl, wired into cmux.json, the config schema, and settings search.

Written for commit 8e0de7c. Summary will update on new commits.

Review in cubic Turn on auto-fix

teamleaderleo and others added 30 commits September 28, 2026 04:29
Behind the new agentActions.turnControl setting (off by default), a Stop
pill appears in a terminal pane's bottom-trailing corner while Claude Code
or Codex is running a turn there. Clicking it sends Escape through the same
named-key path the TextBox uses, then journals the interrupt as a turn
completion for the pane's active hook session: agents run no Stop hook when
interrupted, so the pane would otherwise stay running.

Part of #15223.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review fixes for the Stop button:
- The interrupt now settles every session the journal consumer has running
  on the surface (AgentLifecycleReducerState.userInterruptDrafts), instead
  of guessing the newest hook-store entry. A nested `claude -p` session or a
  relay session no longer gets a phantom idle session while the parent
  keeps running, and a click no longer parses the hook store on main.
- Only Claude Code settles through the journal. Codex journals lifecycle at
  prompt submit and Stop only, so a settle could leave a still-working
  Codex pane idle for the rest of its turn.
- Stop holds for 1.5 s after a click, so a double click sends one Escape
  (a second Escape at Claude's idle prompt opens its rewind menu).
- The pill is raised above the find overlay whenever that overlay is.
- tests/test_cmux_settings_supported_paths.py knows the agentActions section.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	cmux.xcodeproj/project.pbxproj
Journals a Claude Code turn on the focused terminal over the control
socket, checks the Stop pill appears, clicks it and checks it hides, and
checks the pill stays hidden with agentActions.turnControl off. Keeps
before/after window screenshots.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
surface.current never answered in CI; workspace.create returns the new
terminal's ids directly, with the netcat socket fallback.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	cmux.xcodeproj/project.pbxproj
Both CI attempts had the socket answering ping while main-thread verbs
timed out right after launch. Retry activate_app like the focus-history
test before creating the terminal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The fixture harness's /tmp socket passed its readiness check through the
app's own diagnostics while every command from the test runner came back
empty. Put the socket in the runner's temporary directory, as the hook
prompt-length test does, and talk to it over netcat.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The argument domain stored "YES" as a string, which the settings
catalog does not decode, so the pill stayed off.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	cmux.xcodeproj/project.pbxproj
Catch-up merge by scripts/ci/catch_up_pr.py (RFC #14631).
Merged by scripts/merge-main.sh: mf/main at da27bbc.

Resolved conflicts:
- Resources/Localizable.xcstrings: xcstrings key-level union
- cmux.xcodeproj/project.pbxproj: union of added entries, then normalize-pbxproj.py
- Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift: generate-cmux-config-schema.py, regenerated from the merged schema (both sides changed the schema)

Catch-up-previous-head: b3b7a50
Catch-up-base: da27bbc
Catch-up merge by scripts/ci/catch_up_pr.py (RFC #14631).
Merged by scripts/merge-main.sh: mf/main at 8b23dd7, the newest commit with green CI fast guards (2 newer skipped).

Resolved conflicts:
- Resources/Localizable.xcstrings: xcstrings key-level union
- cmux.xcodeproj/project.pbxproj: union of added entries, then normalize-pbxproj.py
- Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift: generate-cmux-config-schema.py, regenerated from the merged schema (both sides changed the schema)

Catch-up-previous-head: 0156a4a
Catch-up-base: 8b23dd7
Catch-up merge by scripts/ci/catch_up_pr.py (RFC #14631).
Merged by scripts/merge-main.sh: mf/main at 0abac32.

Resolved conflicts:
- Resources/Localizable.xcstrings: xcstrings key-level union
- cmux.xcodeproj/project.pbxproj: union of added entries, then normalize-pbxproj.py
- Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift: generate-cmux-config-schema.py, regenerated from the merged schema (both sides changed the schema)

Catch-up-previous-head: c815fcc
Catch-up-base: 0abac32
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 6, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 53 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: bbb15d94-cdc3-4ef2-8d14-1a69fe2a6cae
📥 Commits

Reviewing files that changed from the base of the PR and between a8c4861 and 8e0de7c.

⛔ Files ignored due to path filters (1)
  • Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift is excluded by !**/*.generated.*
📒 Files selected for processing (58)
  • Packages/macOS/CmuxAgentJournal/Sources/CmuxAgentJournal/AgentJournalEventDraft+UserInterrupt.swift
  • Packages/macOS/CmuxAgentJournal/Sources/CmuxAgentJournal/AgentJournalStore.swift
  • Packages/macOS/CmuxAgentJournal/Tests/CmuxAgentJournalTests/AgentJournalStoreTests.swift
  • Packages/macOS/CmuxAgentJournal/Tests/CmuxAgentJournalTests/AgentJournalUserInterruptTests.swift
  • Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/AgentActionsCatalogSection.swift
  • Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/SettingCatalog.swift
  • Packages/macOS/CmuxSettings/Tests/CmuxSettingsTests/SettingCatalogTests.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Resources/Localizable.xcstrings
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift
  • Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift
  • Resources/Localizable.xcstrings
  • Sources/AgentActionsSettingsFileMapping.swift
  • Sources/AgentJournalLifecycleCenter+Projection.swift
  • Sources/AgentJournalLifecycleCenter.swift
  • Sources/AgentJournalOperationReceipt.swift
  • Sources/AgentTurnInterruptTarget.swift
  • Sources/CmuxSettingsFileStore+SupportedPaths.swift
  • Sources/DockSplitStore+Reset.swift
  • Sources/DockSplitStore+RestoredAgentLifecycle.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/KeyboardShortcutSettingsFileStore.swift
  • Sources/Panels/TerminalPanel+AgentTurnControl.swift
  • Sources/SettingsSearchAliases.swift
  • Sources/SettingsSearchIndex.swift
  • Sources/TerminalAgentTurnControlButton.swift
  • Sources/TerminalAgentTurnControlView.swift
  • Sources/Workspace+AgentLifecycle.swift
  • Sources/Workspace+PanelLifecycle.swift
  • Sources/WorkspaceSidebarAgentRuntimeObservationModel.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/AgentDeliveryTTYBindingTests.swift
  • cmuxTests/TerminalAgentTurnControlTests.swift
  • cmuxTests/WorkspaceSidebarProcessTitleObservationTests.swift
  • cmuxUITests/AgentActionPillUITests.swift
  • skills/cmux-settings/references/all-keys.md
  • tests/test_cmux_settings_supported_paths.py
  • web/data/cmux.schema.json
  • web/messages/ar.json
  • web/messages/bs.json
  • web/messages/da.json
  • web/messages/de.json
  • web/messages/en.json
  • web/messages/es.json
  • web/messages/fr.json
  • web/messages/it.json
  • web/messages/ja.json
  • web/messages/km.json
  • web/messages/ko.json
  • web/messages/no.json
  • web/messages/pl.json
  • web/messages/pt-BR.json
  • web/messages/ru.json
  • web/messages/th.json
  • web/messages/tr.json
  • web/messages/uk.json
  • web/messages/zh-CN.json
  • web/messages/zh-TW.json
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Note

Pull Request opener @azooz2003-bit is not an author or co-author of any commit in this PR (commit identities: teamleaderleo, claude). The CLA check will still proceed and requires every listed identity plus @azooz2003-bit to have signed.

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants