Dev builds should use the most permissionless socket mode so coding
agents and external tools can connect without any restrictions.
Changes automation → allowAll in the LSEnvironment plist entries and
the launch-time env vars.
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Fix update attempt refreshing pill without actually updating
The attemptUpdate() subscriber watched for .updateAvailable state to
auto-confirm, but showUpdateFound used setStateAfterMinimumCheckDelay
which delays the transition by up to 2 seconds. During that window,
dismissUpdateInstallation (from a background probe race) could cancel
the pending transition, reverting state to idle without ever confirming.
The subscriber then tore down on the transient idle, silently abandoning
the update.
Fix: move auto-confirm to the Sparkle driver level via an
autoInstallOnNextUpdate flag. When set, showUpdateFound immediately
calls reply(.install) bypassing the delay entirely. The subscriber
is kept as a fallback but no longer tears down on transient idle
while the flag is active.
Closes https://github.com/manaflow-ai/cmux/issues/2166
* Revert "Fix update attempt refreshing pill without actually updating"
This reverts commit 1cd842dd924bf114b096f222851c47d2e36ad4d9.
* Fix update attempt refreshing pill without actually updating
The attemptUpdate() subscriber tore down monitoring whenever it saw
.idle after observing progress. During check startup (retry loop,
background probe race), state can transiently return to .idle before
Sparkle's interactive check begins. The subscriber interpreted this
as "check completed" and stopped monitoring, so the auto-confirm
for .updateAvailable never fired.
Fix: add !state.isIdle to the teardown guard so monitoring only
stops on terminal failures (.notFound, .error), not transient idle.
Closes https://github.com/manaflow-ai/cmux/issues/2166
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* New window inherits size from current window
When creating a new window via Cmd+Shift+N, use the key window's
frame dimensions instead of the hardcoded 460x360 default. The new
window cascades from the existing window's position so it doesn't
stack directly on top.
* Use Ghostty's cascade algorithm for new window positioning
Match upstream Ghostty's window cascade logic: maintain a
lastCascadePoint that tracks where the next window should appear.
First window seeds the point from its own top-left corner, subsequent
windows advance the cascade point via NSWindow.cascadeTopLeft(from:).
On window close, reset the cascade point to the closing window's
position so the next window appears nearby.
New windows still inherit the key window's size so Cmd+Shift+N
creates a window matching the previous one's dimensions.
* Fix frame-to-contentRect conversion and use preferred window resolver
Convert existingFrame to a content rect via
NSWindow.contentRect(forFrameRect:styleMask:) so the new window
matches the source window's actual size instead of growing by
titlebar insets on each Cmd+Shift+N.
Use preferredMainWindowContextForWorkspaceCreation to resolve the
source window, consistent with showOpenFolderPanel and other
callers.
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
The confirm dialog showed a sanitized command (BiDi/zero-width stripped)
but executed the raw string, creating a display/consent mismatch.
Now the command is sanitized once and the same string is used for both
display and execution.
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Handle Cmd+O in handleCustomShortcut to prevent Documents folder open
Cmd+O for "Open Folder" was only handled in SwiftUI menu, which can
fail due to focus bugs when terminal is focused. This caused AppKit's
default NSDocumentController to open the Documents folder instead.
Now Cmd+O is intercepted in handleCustomShortcut like other shortcuts.
Fixes#2010
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix fallback directory loss and deduplicate Open Folder logic
Address review feedback:
1. Pass selected directory URL to fallback window creation so the
user's folder choice is not silently discarded
2. Replace inline NSOpenPanel code in cmuxApp.swift menu action
with a call to AppDelegate.showOpenFolderPanel() to avoid
future divergence between the two code paths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Set NSOpenPanel directoryURL to current terminal working directory
Address review feedback: set panel.directoryURL to the focused
terminal's working directory so Open Folder starts in a contextually
relevant location instead of AppKit's default.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Use shared main-window resolver and openWorkspaceForExternalDirectory in showOpenFolderPanel
Address review feedback: use preferredMainWindowContextForWorkspaceCreation
for directory seeding (works when auxiliary windows are key) and
openWorkspaceForExternalDirectory for workspace creation (ensures
shouldBringToFront and consistent fallback behavior).
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Add set-color/clear-color workspace actions for tab color via CLI
Expose the existing tab color functionality through the workspace-action
CLI command, enabling programmatic tab color setting without the GUI
context menu.
Supports both named colors (Red, Blue, Amber, etc.) and hex values
(#RRGGBB). Named colors resolve against the built-in palette via
case-insensitive matching.
Usage:
cmux workspace-action --action set-color --color blue
cmux workspace-action --action set-color --color "#C0392B"
cmux workspace-action set-color Amber
cmux workspace-action clear-color
* Return explicit null color in clear_color JSON response
Restore "color": null in the clear_color response payload so JSON
consumers can distinguish "color was cleared" from "no color field".
---------
Co-authored-by: Ariel Tobiana <arieltobiana@gmail.com>
The socket API's send-key command only supported enter, escape, tab,
backspace, and ctrl+letter. Arrow keys and shift+tab had to be sent
as raw escape sequences via send_text, but 0x1B is filtered out by
socketTextChunks and converted to a standalone Escape key event,
breaking multi-byte sequences like \x1b[A (ArrowUp).
This commit extends sendNamedKey to handle:
- Arrow keys: up/down/left/right (with arrow_up/arrowup aliases)
- shift+tab / shift-tab / backtab
- home, end
- delete / del / forward_delete
- pageup / page_up
- pagedown / page_down
These map directly to the existing sendKeyEvent infrastructure using
the appropriate kVK_* keycodes and modifier flags.
Co-authored-by: I Luk Kim <yirugi@gmail.com>
* Reduce shell integration prompt latency
Three changes to cut ~10-15ms from every precmd/preexec cycle:
1. Use zsh/net/unix (zsocket) for socket sends when available. Eliminates
fork+exec of ncat/socat/nc for every telemetry send (~3ms per send,
3-4 sends per prompt cycle). Falls back to external tools if the
module is unavailable.
2. Replace _cmux_kill_process_tree (synchronous /bin/ps -ax | awk) with
direct kill in _cmux_stop_pr_poll_loop. The tree-kill enumerated all
system processes on every command (~5-13ms). Orphaned children (gh,
sleep) finish on their own within seconds.
3. Minor savings: guard _cmux_patch_ghostty_semantic_redraw after first
success, make _cmux_clear_pr_for_panel async, cache bash send tool.
* Address review: process-group kill, fix clear_pr race, reorder bash init
1. Use kill -KILL -- -$PID (process-group kill) instead of plain kill.
Background jobs are process-group leaders, so this kills all
descendants (gh, sleep) without /bin/ps overhead.
2. Keep bash _cmux_clear_pr_for_panel synchronous to prevent race
with the next report_pr from the poll loop. Zsh version uses
_cmux_send_bg which is synchronous when zsocket is available.
3. Move _cmux_detect_send_tool after _cmux_fix_path in bash so the
cached tool lookup runs with the final PATH.
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Reject invalid color values at parse time with a clear error message
instead of silently ignoring them. Colors are normalized to #RRGGBB
via WorkspaceTabColorSettings.normalizedHex during decode.
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Pre-launch app for browser UI test on headless CI runners
XCUIApplication.launch() blocks ~60s then fails on headless WarpBuild
runners because foreground activation requires a GUI login session.
Apply the same pre-launch strategy used for the display resolution test:
- CI shell launches the app with env vars before running xcodebuild
- Test detects pre-launched app via manifest, uses activate() instead of
launch() to avoid killing and relaunching the app
- Falls back to clicking the window for focus via accessibility framework
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Revert "Pre-launch app for browser UI test on headless CI runners"
This reverts commit a540e2fd99aaa1395b91a8d50caa797cdd7551b8.
* feat: cmux.json for custom commands
* tests: add cmux json tests
* fix: pr review feedback: validation, translations, input handling, and palette improvements
- Fix Danish ("Overfladedef inition") and Norwegian ("rotmapp") translation typos
- Add empty-string check for baseCwd fallback in command palette handlers
- Coalesce \r\n into single Return keypress in sendInput
- Redact command text from timeout log to prevent secret leakage
- Add decode-time validation: reject hybrid/empty commands, ambiguous layout
nodes, wrong split children count, and empty pane surfaces
- Namespace custom command IDs with "cmux.config.command." prefix
- Forward command description to palette subtitle when available
- Update tests for new validation rules and ID prefix
* fix: address PR review feedback — per-window config isolation, blank validation, ancestor walk,
palette sanitization
* fix: fallback to current dir cmux.json watching if no any cmux.json found in full acesor walk
* ci: trigger CI for fork PR
* Add directory trust for cmux.json command confirmation
The confirm dialog now shows the actual command text and has an "Always
trust commands from this folder" checkbox. When checked, future confirm
commands from that directory skip the dialog.
Trust is scoped to the git repo root if the cmux.json is inside a repo,
so trusting once covers all subdirectories. Non-git directories are
trusted by exact path. Global config is always trusted.
Trusted directories are persisted in ~/Library/Application Support/cmux/
trusted-directories.json.
* Add trusted directories section to Settings
Shows all trusted directories with per-directory revoke buttons and a
Clear All option. Placed in a "Custom Commands" section between
Automation and Browser in Settings.
* Replace trusted directories list with editable textarea
One path per line, with a Save button that activates on changes.
Users can add, remove, or edit paths directly.
* Auto-save trusted directories on edit, remove Save button
Matches the behavior of other textarea settings (browser host
whitelist, external URL patterns) which auto-save via @AppStorage.
* Sanitize command text in confirm dialog against BiDi attacks
Strip zero-width and BiDi override characters from the command preview
so the dialog shows exactly what will be executed.
---------
Co-authored-by: austinpower1258 <austinwang115@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Add Codex CLI hooks integration
Adds `cmux codex install-hooks` to install lifecycle hooks into
~/.codex/hooks.json and enable the codex_hooks feature flag. The hooks
call `cmux codex-hook <event>` which gracefully no-ops (exit 0, prints
{}) when not running inside cmux, so they're safe to leave installed
globally.
Supported events: SessionStart (session tracking), UserPromptSubmit
(set Running status), Stop (completion notification + Idle status).
Install merges with existing user hooks and is idempotent. Uninstall
(`cmux codex uninstall-hooks`) removes only cmux-owned hooks,
identified by the `cmux codex-hook` command prefix.
* Show diff and ask for confirmation before modifying user config
install-hooks and uninstall-hooks now preview changes to hooks.json and
config.toml before applying, with a [Y/n] prompt. Pass --yes/-y to
skip confirmation.
Hook commands use `command -v cmux` guard so they silently no-op
(echo '{}') when cmux CLI is not on PATH (e.g. user runs codex in a
non-cmux terminal or after uninstalling cmux).
* Improve diff output with line numbers and context
install-hooks and uninstall-hooks now show unified-diff-style output
with line numbers and surrounding context lines, making it easier to
see exactly what will change in hooks.json and config.toml.
* Check CMUX_SURFACE_ID in shell guard before calling cmux
The hook shell command now checks [ -n "$CMUX_SURFACE_ID" ] first,
so it short-circuits to echo '{}' without ever invoking cmux when
not inside a cmux terminal. Prevents usage text and socket errors
from leaking into Codex hook output.
* Uninstall reverts config.toml; fix [features] section handling
uninstall-hooks now also removes codex_hooks from config.toml and
shows the diff for both files before asking for confirmation.
buildConfigWithCodexHooks uses exact TOML key matching instead of
substring contains, and inserts after the first [features] header
only (not replacingOccurrences which hit all matches).
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
When pressing Cmd+N for a workspace number that doesn't exist,
the event was not consumed and fell through to Ghostty's goto_tab
binding, which could create a new window. Now the event is always
consumed when the digit matches, preventing unintended window creation.
Fixes#1970
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Pre-launch app for browser UI test on headless CI runners
XCUIApplication.launch() blocks ~60s then fails on headless WarpBuild
runners because foreground activation requires a GUI login session.
Apply the same pre-launch strategy used for the display resolution test:
- CI shell launches the app with env vars before running xcodebuild
- Test detects pre-launched app via manifest, uses activate() instead of
launch() to avoid killing and relaunching the app
- Falls back to clicking the window for focus via accessibility framework
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Revert "Pre-launch app for browser UI test on headless CI runners"
This reverts commit a540e2fd99aaa1395b91a8d50caa797cdd7551b8.
* Fix browser portal leaking to other tabs on Bonsplit tab switch
When switching between Bonsplit tabs within a workspace, portal-hosted
WKWebViews from deselected browser panels could remain visible above the
newly selected tab. This happened because Bonsplit's keepAllAlive mode
hides non-selected tabs via SwiftUI .opacity(0), but the portal layer
renders at the AppKit window level and is not affected by SwiftUI
opacity changes.
Explicitly hide browser portals for deselected tabs in the pane during
tab selection, ensuring the portal visibility state is always in sync
regardless of SwiftUI re-render timing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: austinpower1258 <austinwang115@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Fix panel resize stuttering when tiled with browser panels (#1968)
During divider drag, the portal sync system was doing O(N²) work per
frame: each geometry callback synced ALL web views, and multiple
callbacks fired per layout pass (setFrameSize + setFrameOrigin + layout).
Two changes:
1. synchronizeWebViewForAnchor now only syncs the primary web view and
defers the all-sync. Each panel fires its own geometry callback, so
secondary syncs are redundant on the hot path.
2. HostContainerView.setFrameOrigin/setFrameSize use markGeometryDirtyIfNeeded
which defers the callback to layout(), coalescing 2-3 notifications
per frame into one. An async fallback ensures origin-only changes
(without a subsequent layout) are still delivered.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix premature geometryRevision increment in markGeometryDirtyIfNeeded
Address reviewer feedback (Greptile, CodeRabbit): geometryRevision and
lastReportedGeometryState are now only updated when the callback
actually fires, not eagerly. This prevents updateNSView from seeing a
premature revision delta and triggering a redundant synchronizeForAnchor
before the coalesced notification arrives.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent Japanese IME confirmation Enter from executing command
Korean IME commits a syllable and executes on a single Enter, but
Japanese/Chinese IME use Enter only to confirm conversion — a second
Enter is needed to execute. Restrict the extra Return forwarding in
shouldSendCommittedIMEConfirmKey to Korean input sources only.
* refactor: use case-insensitive check for Korean input source ID
* Fix dock icon not auto-switching with system dark mode
The automatic icon mode relied on the asset catalog to handle
appearance-based icon selection, but the compiled Assets.car does not
include dark variant renditions for AppIcon. This meant setting
applicationIconImage to nil had no effect — the icon stayed on the
light variant regardless of system appearance.
Replace the nil-reset approach with an active KVO observer on
NSApp.effectiveAppearance that programmatically swaps between
AppIconDark and AppIconLight images when in automatic mode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Address review feedback: fix async race and harden singleton
- Add guard in async callback to skip stale updates after stopObserving()
- Add private init() to prevent external instantiation of singleton
- Remove unused .new KVO option
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: CHE-3 <schumannzheng@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* reload.sh: default to build-only, add --launch flag to open app
By default, reload.sh now builds and prints the app path without
launching. Pass --launch to get the previous behavior (kill existing
instance and open). This lets agents build without stealing focus,
and the user can cmd-click the printed path to launch when ready.
* CLAUDE.md: use reload.sh output for app path instead of hardcoded home dir
The templates hardcoded /Users/lawrencechen/ which broke cmd-click
on machines with a different home directory. Agents now read the
actual path from reload.sh's "App path:" output.
* CLAUDE.md: add concrete example for app path URL format
Uses a fictional /Users/jane/ to make it clear the path comes from
reload.sh output, not a hardcoded value.
* CLAUDE.md: clearer step-by-step instructions for app path URL
Explicit 3-step recipe (grab path, prepend file://, format as link)
with example showing the reload.sh output and the expected result.
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Remove fork PR guards from CI workflows
Fork PRs are already gated by GitHub's "Require approval for outside
collaborators" setting. The workflow-level guards were redundant and
prevented WarpBuild jobs from running even after maintainer approval.
* Address review feedback: extend guard test, skip upload on fork PRs
- Guard test now covers build-ghosttykit.yml and ci-macos-compat.yml
(not just ci.yml)
- Skip xcframework upload when GHOSTTY_RELEASE_TOKEN is unavailable
(fork PRs), so the build still validates without failing at publish
* Check GHOSTTY_RELEASE_TOKEN at runtime instead of step if
secrets context can't be reliably used in step if: conditions.
Check the env var inside the script instead.
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
The Claude Code hooks section referenced pre-0.60 configuration using
matchers (idle_prompt, permission_prompt) that are no longer valid in
current Claude Code versions. Replace with a link to the official
Claude Code documentation.
Fixes#2009
Co-authored-by: BillionClaw <267901332+BillionClaw@users.noreply.github.com>
When MainWindowContext.window (weak var) becomes nil after extended
uptime, resolvedWindow(for:) falls back to windowForMainWindowId()
which searches NSApp.windows by identifier. However, the recovered
window was only assigned back to context.window without reindexing
the mainWindowContexts dictionary — leaving the ObjectIdentifier key
stale. Subsequent lookups via contextForMainTerminalWindow() would
miss the context, causing addWorkspaceInPreferredMainWindow() to
return nil and Cmd+N to fall back to opening a new window.
Call reindexMainWindowContextIfNeeded() when re-resolving a window
so the dictionary key matches the current NSWindow object.
Fixes#1929
Co-authored-by: CHE-3 <schumannzheng@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Export CMUX_SOCKET alongside CMUX_SOCKET_PATH in terminal environment
The app only exported CMUX_SOCKET_PATH when setting up the terminal
environment, but some scripts and hooks (e.g. claude-hook) expect
CMUX_SOCKET. The CLI launcher code already exports both (cmux.swift
lines 9288-9289), but the app-side terminal setup was missing the
alias. This caused claude-hook stop to fail with 'TabManager not
available' when CMUX_SOCKET was empty.
Fixes#1905
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Cache socketPath to avoid redundant call
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix sidebar notification persisting after being read
latestNotification(forTabId:) fell back to latestByTabId when no
unread notifications existed, causing read notifications to persist
in the sidebar even after the user marked them as read, killed all
processes, or switched branches. The sidebar should only display
unread notifications.
Remove the fallback to latestByTabId so the sidebar notification
text clears once all notifications for a workspace are read.
Fixes#1642
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Update test expectation for unread-only latestNotification semantics
After markRead, latestNotification(forTabId:) now returns nil since
it no longer falls back to read notifications. Update the test
assertion to match.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: CHE-3 <schumannzheng@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(browser): use native value setter for React/Vue/Angular compatibility
fill, type, and select commands set input values via direct property
assignment (el.value = x), which does not trigger state updates in
frameworks that override the value setter on element instances.
Use Object.getOwnPropertyDescriptor on the prototype to call the
native setter, which bypasses the framework override and allows the
subsequent input/change events to propagate correctly through
React's synthetic event system.
Affects: browser.fill, browser.type, browser.select
* walk prototype chain instead of instanceof for cross-realm and web component support
---------
Co-authored-by: joshuaswanson <joshuaswanson@users.noreply.github.com>
* Fix all split panes appearing focused after layout restoration
Ghostty C surfaces default to focused=true (Terminal.zig), but
TerminalSurface.lastFocusState was initialized to false. During
restoration, unfocus() is called before C surfaces exist (views not in
window yet), so the ghostty_surface_set_focus(false) call was silently
dropped. When surfaces were later created, the dedup guard
(false != false) prevented the unfocus from ever reaching the renderer.
Three changes:
- Initialize lastFocusState=true to match Ghostty's default
- In setFocus(), update lastFocusState before the surface-nil guard so
the desired state is tracked even when the C surface doesn't exist yet
- In createSurface(), sync focus state after creation so surfaces that
were logically unfocused before the C surface existed get the correct
state applied
Fixes https://github.com/manaflow-ai/cmux/issues/2080
* Sync focus state unconditionally in createSurface
Per review feedback: always call ghostty_surface_set_focus(createdSurface,
lastFocusState) instead of only syncing when !lastFocusState. This avoids
coupling to Ghostty's default focused=true and is more robust if the
upstream default ever changes.
* Keep lastFocusState in sync with AppKit responder focus calls
The becomeFirstResponder, resignFirstResponder, and ctrl-key fast paths
call ghostty_surface_set_focus directly without updating lastFocusState.
This could cause the dedup guard in setFocus() to skip a needed call, or
createSurface() to replay a stale state on surface recreation.
Add recordExternalFocusState() and call it alongside each direct C focus
call so lastFocusState stays authoritative.
* Rename lastFocusState to desiredFocusState
The variable now tracks focus intent (may be set before the C surface
exists), not just the last-applied state. Update the name and doc
comment to reflect this.
---------
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Add tty field to surface items in system.tree JSON response,
reading from existing surfaceTTYNames dictionary. Also show
tty= in the CLI text tree output for terminals that have a
registered TTY.
This enables external tools (e.g. CodeV) to cross-reference
claude process TTYs with cmux surfaces for accurate session
detection when multiple sessions share the same working directory.
* Add -r shorthand to SKIP_SESSION_ID check in claude wrapper
The wrapper checked for --resume but not its -r shorthand, causing
claude -r to fail with a --session-id conflict error because the
wrapper injected its own --session-id alongside the implicit --resume.
Fixes#1987
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Remove invalid -r=* pattern from SKIP_SESSION_ID check
Short options don't use the = form, so -r=* would never match a real
CLI invocation. Keep only -r as the shorthand for --resume.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>