Repository navigation
acpmux: no-ask start modes by inherit or an approved preset; pending prompts reach peers - #20653
teamleaderleo wants to merge 247 commits into
Conversation
… 5xx (#20412) The 0.65.2 RC publish (run 38501752257) failed in the release notes: a new RC line summarizes ~2,000 commits since the lines' merge base, and GitHub answered a 25-commit association query with HTTP 502. Locally the same range completes with 10 commits per query and 5xx retries (1,328 PRs, ~19 min). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…oads (#20259) * test(cmux-tui): reproduce checkpoint capture on oversized kitty upload * fix(cmux-tui): recover checkpoints from unreplayable terminal input * test(cmux-tui): cover safe checkpoint recovery boundaries * fix(cmux-tui): fence checkpoint replay recovery * fix(cmux-tui): reset metadata on checkpoint abort * fix(cmux-tui): reset metadata on checkpoint abort * cmux-tui: refuse local replay recovery for hosted terminals * cmux-tui: journal synthetic replay recovery bytes * cmux-tui: apply hosted rustfmt to checkpoint recovery * test(cmux-tui): keep checkpoint recovery attachment test bounded * cmux-tui: fix journal recovery regression test * fix(ghostty-vt): recover oversized Kitty replay * fix(cmux-tui): detect Kitty replay overflow * fix(ghostty-vt): reject completed Kitty overflow recovery * test(cmux-tui): avoid replay size assumption * test(cmux-tui): tolerate merged checkpoint output frames * style(cmux-tui): format checkpoint attachment assertion * fix(cmux-tui): satisfy checkpoint test lint * test(cmux-tui): attach recovery assertion to live surface * fix(ghostty-vt): reject recovery after prior Kitty overflow * test(ghostty-vt): cover recovery after lost multipart prefix * test(ghostty-vt): import replay error for overflow regression * fix(ghostty-vt): fail closed after lost Kitty prefix * test(cmux-tui): cover checkpoint recovery concurrency cuts * style(cmux-tui): format recovery blob lookup * fix(cmux-tui): preserve journal order across checkpoint recovery * test(cmux-tui): cover contextual checkpoint retry errors * fix(cmux-tui): retry contextual checkpoint ingress races
* test: cover Claude transcript cwd selection * fix: resume Claude from transcript project cwd * test: cover reused legacy agent PIDs * fix: validate legacy agent PIDs by process identity * fix: unwrap candidate resume directories * fix: avoid ambiguous resume candidate closure * test: cover Claude resume review regressions * fix: address Claude resume review findings * fix: use canonical Claude project slugs in restore * test: reproduce process exit after restore identity capture * test: avoid redundant process probes during restore * fix: prioritize absent PIDs and probe presence only when needed * test: retain the mismatched PID reclamation safety probe
* test: allow shared team members through Cloud gate * fix: let accepted team members enable Cloud * test: keep personal team behind Cloud upgrade gate * fix: keep personal Cloud scope behind Pro gate * fix: explain shared team Cloud access
…1NN hangs (#20238) * Bump Bonsplit: stop per-render tab-strip work behind hang reports Pins manaflow-ai/bonsplit#287 (663bc41): context-menu state is built when a menu opens, the hint monitor no longer reads user defaults on every tab-bar init, and the whole-tab hint animation is gone. Sentry: CMUXTERM-MACOS-41NN, -2QV2, -2QAN, -2SCZ, -2PRX, -26QQ. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bump Bonsplit: resolve the hint symbol when the monitor starts Pins manaflow-ai/bonsplit#287 review follow-up (9d90480). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Diagnose sustained tab-bar render storms * Tighten tab-bar render storm diagnostics * Name tab-bar diagnostics as render windows --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* test: reproduce custom upload copy into missing directory * fix: keep custom upload paths standalone * fix: preserve custom upload extension names * test: cover bounded custom upload extensions * fix: bound custom upload remote extensions * fix: append bounded extension scalars
…ast the budget (#20435) * test(cloud diagnostics): ingest refusals name their cause and keep failures and drop reports Red on main at the real handler with Postgres: the byte-budget and ingress 429s carry no refusal code, routine detail that fills the minute refuses a failure batch, and a sampled-out poll that reports queue drops is discarded. * Name every Cloud diagnostics 429 and keep failures and drop reports past the budget Production 429s on POST /api/observability/cloud all come from the shared cmux-analytics firewall rule, which is keyed by connection IP; the route span could not say so. The route span now records the refusal cause (ingress_rate_limited with the rule ID, account_byte_budget, event_conflict, storage_unavailable), the keyed account hash and the install ID. Batches with a failure, timeout, cancellation or sign-in record get a 128 KiB reserve above the 256 KiB routine minute budget, so routine success detail cannot refuse them. A sampled-out poll span that carries the client's dropped count is now stored, since it is the only record of that loss. * Count every Cloud diagnostics refusal despite head sampling; keep evidence from over-budget batches Review follow-up. The route span is head-sampled at 2%, so a refusal now also emits one always-kept cmux.cloud_diagnostics.refusal span with the cause, firewall rule, account key, install ID and counts. The firewall rule travels as the ingress refusal value instead of a span side effect. The byte budget no longer lets routine spans spend the reserve: a batch over the routine budget stores only its failure, timeout, cancellation and sign-in records within the reserve, commits them, and still answers 429, so the client's retry deduplicates them. The install ID is labeled cmux.client.instance_id so it cannot be confused with the server's service.instance.id. Sampling docs name the drop-report exception. * Coalesce repeatable Cloud diagnostics refusal spans; drain evidence kept from refused batches Second review follow-up. A firewall refusal runs before authentication, so an always-kept span per refusal let an anonymous flood become kept Axiom spans. Firewall and malformed-body refusals now emit at most one span per cause, rule and account per 10 s on each instance, with cmux.telemetry.refusal_count. Budget, conflict and storage refusals stay one span each. Evidence stored from a budget-refused batch schedules a drain, and the refusal reports it as cmux.telemetry.stored_spans instead of accepted_spans.
* test(cloud): reject stale terminal projection * fix(cloud): recover wedged workspace links Drop timed-out local links, avoid reprojecting stale terminal inventory, and restart a live but unresponsive guest daemon after bounded health failures. Preserve the remote registry and terminal hosts for the next connection. * cmux-tui: test that registry reopen cost ignores terminal output history Reopening a registry reads every page of the file today: an unscoped PRAGMA quick_check and an unconditional journal_event_index backfill. A production machine with 3.6M terminal.output records took ~3 minutes of single-threaded CPU to start. This test measures reopen page-cache misses after 16 and 16Ki output records and fails on main. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * cmux-tui: bound daemon startup by recent work, not journal size A production registry held 3.6M terminal.output records (12 GB) and the daemon took ~3 minutes of single-threaded CPU to start. Three open-time steps read the whole journal: - create_session_journal_schema re-inserted every journal row into journal_event_index on every open (twice). Appends write the index in the same transaction, so a full backfill runs only when the index table is new; an existing index repairs only rows past its highest sequence. - PRAGMA quick_check read every page of the file. The check now runs per registry state table and its indexes, skipping the journal history tables (session_journal, its subject and event indexes, segments, checkpoints, content blobs), whose records are validated on read. - Agent-roster replay decoded and hashed every terminal.output chunk since the roster cursor, at startup and under the registry lock on the next agent event. The roster ignores terminal output (terminal-runtime producer, fixed format), so replay reads only the other records through two (kind, sequence) index ranges and then stands on the target sequence. Reducer semantics are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(cloud): recover wedged cmux-tui links safely * fix(cloud): format journal archive fast path * fix(cloud): use imported journal kind set * test(cloud): expose incomplete canary rollback * fix cloud workspace recovery review findings * test(cloud): exercise stale restored pane recovery * style: format filtered journal query * fix(cmux-tui): skip archived terminal output during roster replay * test(cloud): expose helper failure after canary stop * fix(cloud): harden link teardown and journal replay * style(cmux-tui): apply hosted rustfmt * test(cloud): expect requestTimedOut for persistent request deadlines The persistent resource connection now reports an expired request budget as LinkError.requestTimedOut. Add the case to the isolated deadline fixture's LinkError stub and update the deadline tests to expect it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix cloud recovery review findings * chore(cloud): promote devbox bake with daemon health watchdog Records the verified Freestyle devbox ladder from cloud-vm-image-contract run 38514254364, baked from bc4c6b1 with this branch's cmux-devbox-boot health watchdog and coreutils change, and demotes the previous defaults. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(cloud): promote verified devbox image --------- Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: reproduce Claude helper reopening a stopped turn * fix: ignore Claude helper tools after turn completion * fix: persist Claude turn settlement across notifications * fix: persist Claude stop transitions
* test(coderouter): cover Claude 408 and 409 failover * fix(coderouter): fail over Claude timeout responses
* test(cloud): cover unresponsive VM status and pause probe * fix(cloud): surface wedged guests and bound pause * fix(cloud): bound Go pause preparation * test(cloud): align stats deadline expectation * fix(cloud): preserve pause probe timeout * docs(cloud): cover workflow test helpers
* test: cover control-created surface startup lifecycle * fix: keep control-created surfaces alive until input * fix: cover startup inheritance for control splits * fix: suppress startup replay for auxiliary control terminals Pane-swap placeholders and mobile-created terminals are automation-owned surfaces too, so they must not replay a source surface's one-shot startup payload. Co-authored-by: Austin Wang <austinwang115@gmail.com> (cherry picked from commit 73e5bd2acb5ee0f3f7aefa55ca50e48ebcb7c85c)
* test(opencode): cover restore event before TUI route * fix(opencode): retry restore binding after TUI startup * fix(opencode): wait for TUI ownership changes * fix(opencode): flush queued starts at event boundaries * fix(opencode): cancel queued starts before session end * fix(opencode): observe reactive TUI readiness * test(opencode): exercise real reactive readiness and disposal * fix(opencode): clear queued startup expiry on disposal
* Keep a stale Codex record from resuming the current binding #20246 made a restore treat the agent as running when the session index was not authoritative, so a cached quit index could not mark a live session stopped. It read authority from isComplete(), which for Codex is also false whenever the panel's recorded checkpoint fails verification. A fresh index holding only a rejected stale record then left wasAgentRunning nil, and restore replayed the dead current binding's resume command. Base authority on whether the kind's hook store was observed, which is what #20246 meant; per-panel checkpoint gaps no longer count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix the Muse resume and minimum-contrast render tests The Muse vault test expected an unquoted resume command, but registered agents have quoted each argument and prefixed a cd since August; assert the exact command as the sibling vault tests do. The minimum-contrast test never rendered on Blacksmith runners: the app-host process is not the active app there, so its window is never key and never reported visible, and the renderer stays released. Let it opt into a window that reports on-screen occlusion, as the simulator theme tests already do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix four intermittent main CI failures - cmuxd-remote: a failed async attach write queued its error and then cancelled the connection, and dispatch could observe the cancellation first and return context.Canceled. Report the queued error instead. - ws_pty_test: macOS sh can print a job-control warning between the SIZE marker and stty's output; write both with one printf. - Repeated pastes: four jobs share one FIFO whose deadline starts at admission, so a slow runner expired the fourth. The test checks payload fidelity, so its deadline ends only on cancellation. - Unread sidebar test: allow WorkspaceContentView re-evaluations caused by unrelated Workspace publishes in the measured window, counted directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Name what keeps a closed window's manager alive during a Cloud create This test fails on every main run since Cloud was enabled for it (#19988), and reading the code did not find the owner. Report whether the shared controller or app state holds it, and whether it is freed after the create completes or after those are reset, so the next run names the holder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep never-inspected Codex evidence non-authoritative at quit Review follow-up: the previous commit made every per-panel Codex gap authoritative, including records outside the bounded verification pass or whose verification was unavailable. Those can really miss a live session. Track them separately, and let only an inspected, rejected checkpoint count as observed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Build Claude project fixtures with Claude's real slug The fixtures named a Claude project directory by replacing only / and ., but Claude (and ClaudeProjectSlug) replaces every non-alphanumeric scalar. On runners whose temp path holds an underscore the fixture directory was one Claude never creates, so the transcript-derived cwd fell back to the launch cwd and claudeRestorableIndexMapsNestedTranscriptPathToProjectCwd failed. Share one independent slug helper across the four test files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Check the closed manager after the close's own deferred work The Cloud create path holds the manager only weakly. The test sampled it in the same main-actor job as commitMainWindowClose, while the close's autoreleased notifications (object: the manager) and queued main-queue work still held it, so it read as leaked until the next turn. Drain the close in an autorelease pool and wait one main-queue turn, with the create still parked, before checking. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…0482) * style(cmux-tui): apply rustfmt to session journal backfill main fails the cmux-tui cargo fmt check here, which stops hosted verification before any test runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: reproduce untrusted cmux Codex hooks Codex applies -c overrides by splitting the key on every '.', so the session-flag trust that cmux-tui and the ssh-tmux helper pass as hooks.state."/<session-flags>/config.toml:...".trusted_hash never lands and every injected hook needs review. The wrapper test now parses -c the way Codex does. Also cover a cmux-tui reinstall moving its hook group behind the cmux app's group in a shared hooks.json, which shifts the app's hook off its trusted key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: key Codex hook trust by the default home verbatim Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: trust every cmux Codex hook Codex sees cmux-tui and the ssh-tmux helper passed session-flag hook trust as -c hooks.state."<key>".trusted_hash. Codex splits -c keys on every '.', including inside the quotes, and each key contains one (config.toml, hooks.json), so none of that trust or the matching enabled=false applied. Codex listed each event twice and asked the user to review the injected copy. Pass the whole state as one inline table at hooks.state, which Codex merges with the persistent trust. A cmux-tui reinstall also dropped its hook group and appended a new one, moving it behind hooks another producer (the cmux app) added later and shifting that hook off its trusted positional key. Reinsert the group in the slot it came from. The cmux CLI keyed trust by the fully resolved hooks.json path, but Codex resolves only an explicit CODEX_HOME and uses $HOME/.codex verbatim, so a symlinked home left every app hook untrusted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* test: Cloud creation actions must not repeat the welcome * fix: show the Cloud welcome only once across entry points * fix: preserve shared-team Cloud welcome routing
* test: push a new machine's prompt name as root The prompt name push writes the root-owned /etc/cmux. It ran as root until #16769 moved public vm exec to the work user, and has failed with a permission error since; a dev backend's machines then keep cmux@cmux. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cloud): push the prompt name as root again #16769 moved public vm exec to the work user so commands see the same home as Cloud terminals. The create and rename prompt pushes share that exec and write the root-owned /etc/cmux, so they have failed since. Production hid it behind the guest's reflection fetch; dev backends can't be reached that way and kept cmux@cmux. Restore root for exactly these pushes through an explicit asRoot exec option; user vm exec stays on the work user. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#20361) * test(cloud): cover hard restart for frozen VMs * fix(cloud): recover frozen VMs through provider restart * fix(cloud): finish restart health alert integration * fix(cloud): satisfy Swift lifecycle timeout typing * fix(cloud): make fallback restart retries idempotent * test(cloud): reject unverified recovery and stale restart links * fix(cloud): fail closed without provider hard restart * test(cloud): expect restart capability to fail closed * fix(cloud): keep health alerts snapshot consistent * docs(cloud): describe restart capability honestly * test(cloud): cover capability-gated recovery alerts * test(cloud): reject pause resume guidance for frozen guests * fix(cloud): preserve frozen machines in recovery guidance
* test(cloud): a full shared pool is not reported as a missing plan Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cloud): explain a full shared pool instead of a missing plan A 402 vm_resource_pool_exceeded on a Pro team read as "Your plan does not allow this Cloud operation." because the client classified every 402 by status alone. Read the server's error code: a full pool names the vCPUs or memory in use and the request (reusing the sheet's pool shortfall copy), active-limit, create-credit and size-gated refusals get their own sentence, an unrecognised code keeps the server's message, and only a code-less 402 keeps the generic plan sentence. The pending create row offers Retry first and an upgrade second for a full pool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cloud): report a full pool as resource_limit and redact server copy Address review: the server's telemetry contract rejects an unknown failure class and drops the whole batch, so a full pool or active-limit refusal is classified as the existing resource_limit instead of a new capacity class. The open-workspace path now applies the same redaction fallback as create, and a full pool offers an upgrade only once the plan is known to have a higher tier. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cloud): key create failure actions on the parsed refusal code Address review: the pending create row matched refusal codes as substrings of the display text, so a longer code such as vm_resource_pool_exceeded_v2 counted as a full pool. Read the code once from the exact (HTTP 402: <code>) reference the CLI and the in-process create both print, and compare it whole. Also translate the four new failure strings into every locale the catalog carries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cloud): carry the refusal code apart from the failure text Address review: reading the code back out of the failure text let a server message that named another code stand in for the real one. The in-process launcher now reads the code from the thrown error and the CLI path reads only the reference ending the CLI's own Error: header; both pass it through the create completion onto the operation, and the failed row's actions key on that carried code alone. A retry clears it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cloud): cover the refusal code handoff to the failed row Address review: assert the app coordinator carries a launcher's refusal code onto the pending row and clears it on retry, that finish overwrites a code, and that an open failure on an existing machine keeps none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cloud): cover pool upgrade and credit billing scope regressions * fix(cloud): preserve pool upgrades and billing scope * test(localization): cover Unicode escapes in Swift defaults * fix(localization): decode Swift Unicode scalars in defaults * fix(cloud): localize personal create credit refusals * test: correlate v2 mock responses with request ids * Keep localization additions in main catalog order * Start CodeRouter children from a clean signal mask and the caller's stdio The two posix_spawn sites #18953 added in the CLI handed the child the calling Swift concurrency thread's signal mask, so a CodeRouter child could not receive SIGTERM or SIGWINCH (CLIAuthAliasTests: termination signal) and the exec/spawn signal-state guard failed. Set an empty mask and default dispositions on both, as the other CLI spawn sites do. The alias exec also decided which standard descriptors to pass on at spawn time, so a file the CLI had opened since launch could reuse a closed stdin and reach the child as an open one. Record the caller's standard descriptors at the top of main and pass on only those. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Assert that `coderouter add` stays off the socket handoff #18953's classification test expected `add codex` to arm the handoff, contradicting its own name ("only exact top-level agent names use socket handoff") and the launch policy. CodeRouter's handoff parser accepts only a provider name after `--`, and `add` does its own login without a handoff lease, so the policy is right and the expectation was wrong. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Probe a closed stdin without being fooled by the script itself With descriptor 0 closed, /bin/sh opens the script it runs on that descriptor, so the fake CodeRouter's `(: <&0)` probe always reported an open stdin on macOS and the test could never pass. Treat stdin as open only when descriptor 0 exists and is not the script. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: echo request ids in capabilities fixture --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* test: cover split actions targeting projected remote tmux panes * fix: route split actions through projected remote tmux pane owners Cmd+D and menu splits pass the focused terminal surface ID. A remote tmux pane is projected from its mirror and is absent from workspace.panels, so the shared split action rejected it before sending a remote command. Resolve projected panes through the existing control topology and dispatch to their mutation owner. Preserve the exact target, split orientation, before placement and focus intent; rejected requests never fall back to a local split. Verification: changed Swift syntax and git diff --check passed. A standalone probe executing the actual split action with synthetic collaborators ran 27 routing assertions: 16 failures on regression commit 4162b2d, zero with the fix. The probe does not compile or exercise the native app. App-host regressions were added but not executed; full Xcode is unavailable locally. * test: cover remote tmux keyboard split routing Co-authored-by: Mirksen <52635146+Mirksen@users.noreply.github.com> --------- Co-authored-by: Austin Wang <38676809+austinywang@users.noreply.github.com>
BTreeSet is already imported, and clippy's -D unused-qualifications fails the cmux-tui lint job on main. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…20502) #20376 gave a persistent channel's per-request deadline its own error, LinkError.requestTimedOut, separate from the connect timeout. The app already classifies it as a timeout and as retryable, but this test still caught only .timedOut, so the deadline it waits for escaped as an uncaught error. Catch the error the channel now throws, and check that same error is retryable. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…20499) * Add regression for Codex restore failing on coordination-lock contention Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Retry Codex lock-namespace contention before refusing a restore During a relaunch dozens of Codex threads resume at once, each briefly holding thread-writer-locks/.coordination.lock. The CLI's final exec guard treated that `.changing` probe like an unreadable lock and refused an unowned conversation. It now re-probes `.changing` for a bounded ~3.5s, woken early by lock-namespace file events, while `.active` and `.unavailable` still refuse at once with case-specific messages. The app admission path already kept `.changing` recoverable, but waited up to 8s per round for a file event a flock release never raises; it now re-probes after 250ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Re-probe lock-namespace changes inside one admission request Review found that a 250ms evidence bound turns a coordination lock that never settles into ~4 admission RPCs per second per pane. Keep the 8s request bound and instead re-probe `.changing` on the settle bounds within the same request, with an app-host regression for the waiting request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* test: cover OpenCode lifecycle notification regressions
* fix: restore OpenCode lifecycle notifications
Handle current OpenCode completion and failure events, coalesce duplicate idle callbacks, and preserve error state through the feed and TUI journal bridges.
Co-authored-by: e-jung <e-jung@users.noreply.github.com>
* fix: finish OpenCode notification repair
Route lifecycle plugins through OpenCode's XDG config directory, preserve failed turns in Feed, and cover the current error event shape and ordering.
Co-authored-by: e-jung <e-jung@users.noreply.github.com>
* fix: share OpenCode failure detail extraction
Use the same non-optional failure message in notification and Feed delivery, and let the coalesced idle fixture finish on a session-end marker.
Co-authored-by: e-jung <e-jung@users.noreply.github.com>
* test: wait for OpenCode failure hook
* test: cover OpenCode server entry notifications
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: activate OpenCode server plugin entries
Released OpenCode (1.x and the dev channel) loads cmux through each
plugin's default server(ctx) entry inside the TUI process. Those entries
returned {}, so no lifecycle event reached cmux. Subscribe when the TUI
process server context is present, once per process.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(opencode): publish one terminal stop per turn
A turn now settles on its first terminal event and is reopened only by a turn
identity the session has not seen (a new user message or prompt). Repeated
message updates and busy status no longer clear the recorded stop, and a
failure after a published idle stop is dropped instead of producing a second
notification. Applies to the Feed bridge and the session plugin source.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(opencode): redact credentials in upstream failure text
OpenCode error text reaches notifications and Feed error fields. Redact
credentials, bearer tokens, JWTs and emails first, keep paths and model
names readable, and fall back to product copy when nothing safe remains.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(opencode): record OpenCode's own launch for resume
On OpenCode 1.x the TUI process argv is [opencode, /$bunfs/root/src/index.js],
and the sanitizer only dropped the V2 TUI worker entry, so restore replayed
the Bun virtual path as a project directory. Drop any Bun virtual entry.
Also stop reusing an inherited launch capture that describes another agent,
which recorded e.g. a Claude command for an OpenCode session started from a
Claude shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cmux-tui): keep shutdown-interrupted OpenCode turns running
OpenCode retains an execution interrupted by shutdown and resumes it, so
map that interruption to agent.state.changed instead of completing the turn.
Read the structured reason before redaction replaces free-form strings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test: register the OpenCode server-entry harness as manual
No workflow names tests/test_opencode_server_entry.py, so the registry
guard rejected its linux-guard lane.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: resubscribe OpenCode server entries after scope reinit
The process-global guards blocked a reinitialized plugin scope from
subscribing again. Key the owner on the plugin ctx, which every loader
in one scope shares, and replace the previous feed when it changes.
The regression harness now waits on a session.deleted sentinel instead
of a fixed delay and keeps its socket in the temporary directory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(opencode): open turns on run signals, not message updates
A queued prompt's admission and user message arrive while the previous turn
is still running, so keying turns on first-seen message IDs dropped the stop
for the queued turn. Open a turn only on a run signal (session creation, busy
status, a promoted prompt or an execution start) in all three bridges; a
repeated message update after a stop still cannot reopen the turn. The TUI
bridge tracks turn starts before its ownership check so a turn that starts in
a hidden tab keeps its stop.
Also:
- Do not publish a stop for an execution interrupted by shutdown, which
OpenCode resumes, matching cmux-tui.
- Redact credentials in the Feed plugin's own failure text, which reaches
Feed without passing through the CLI.
- Drop inherited subrouter and routed-account launch keys when recapturing
OpenCode's own launch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(opencode): cover queued prompts and shutdown interruptions
Drive a prompt that is admitted during a running turn and promoted after its
stop, a repeated user-message update after a stop, a late failure, and a
shutdown interruption through the session plugin and the Feed bridge. Wait
for a session-end sentinel before counting so a late extra stop is caught.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: bind OpenCode hooks to this process and plugin scope
An OpenCode started from another OpenCode inherited the parent's
opencode launch capture and recorded the parent's resume command.
Always capture this process's launch; no cmux wrapper exports one.
OpenCode never disposes a replaced plugin scope's hooks, so the session
bridge now ignores events once a new scope owns the process.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test: cover raw OpenCode failure events in Feed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: share OpenCode failure classification with Feed
A raw session.error, session.execution.failed or session.next.step.failed
stop made the notification an error but reached Feed as a successful
Stop. Both paths now use one predicate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test: cover OpenCode interrupts and concurrent plugin scopes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: keep OpenCode interrupts and concurrent scopes out of failures
OpenCode publishes session.error for a user interrupt (MessageAbortedError)
and a recoverable context overflow (ContextOverflowError), then goes idle.
These turned an Esc into a red error notification. The bridges now let the
idle stop decide the outcome.
OpenCode keeps one plugin scope per directory and disposes a scope's
hooks on teardown, so dedupe only within one ctx instead of evicting the
other live scope.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: e-jung <e-jung@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* test: cover browser Return routing and native form submission * fix: deliver browser Return to the owning web view * test: keep Return out of Web Inspector page routing * fix: preserve Web Inspector Return routing * test: cover inspector Return in browser focus mode * fix: keep Web Inspector outside browser focus mode * fix: compile browser inspector focus guard * test: keep foreign web content Return out of browser focus mode Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: use structural ownership for browser Return routing Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* test: don't save or restore a Claude session that never wrote a transcript Claude Code writes its transcript only after the first message. A tab whose Claude was started and quit before any prompt keeps its agent-hook binding, so relaunch types 'cmux restore claude <id>' and Claude fails with 'No conversation found'. Expected red until the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: don't save or restore a Claude session that never wrote a transcript Claude Code writes <config>/projects/<slug>/<id>.jsonl only after the first message. A session started and quit before any prompt kept its agent-hook binding, so relaunch typed 'cmux restore claude <id>' and Claude failed with 'No conversation found'. - The agent index records Claude sessions whose hook record names where the transcript goes (transcript_path or CLAUDE_CONFIG_DIR) but finds none in any config root, with the paths to re-check. - Workspace and Dock session snapshots drop that session's Claude agent and agent-hook bindings after the snapshot is built, re-checking the paths first so a first prompt sent after the load still restores. An older session on the surface is never promoted in its place. A cached index marked incomplete carries no such evidence. - cmux restore claude checks every config root the record and shell name and fails early with a localized message, retiring only the binding for that checkpoint, instead of launching Claude. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: address review of transcript-less Claude restore - cmux restore claude checks only when the session names its transcript location (the hook-store transcript_path or a captured CLAUDE_CONFIG_DIR) and accepts the reported transcript directly, so a session in a root the lookup cannot see is never refused or unbound. - A cached index marked incomplete keeps the transcript-less evidence. The filter re-checks the expected paths, so update relaunch and the quit deadline fallback drop the empty session without discarding one whose first prompt landed later. - Closed-tab history for workspace and Dock panels applies the filter. - Neutral error wording, since a live session elsewhere has no transcript yet either. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Add regression test for Vietnamese IME Backspace dropping preedit macOS Vietnamese Telex commits its marked text on Backspace, then sends deleteBackward:. The committed text is currently attached to the Backspace key event, which Ghostty's encoder drops. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix Vietnamese IME Backspace wiping the preedit macOS Vietnamese Telex handles Backspace on underlined text by committing the preedit with insertText and then sending deleteBackward:. keyDown sent the commit as a Backspace key event with UTF-8 attached, which Ghostty's encoder drops, and doCommand ignored deleteBackward:, so the whole word vanished. When interpretKeyEvents both commits text and asks for deleteBackward:, send the commit as typed text and then the Backspace on its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* test: Freestyle TLS rule limit defaults to the raised 200,000 cap Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cloud): raise the Freestyle TLS rule limit default to 200,000 Freestyle raised the account cap from 2,000. The old default made the operator alert page as "limit reached" at 2,367 rules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* test: cover upright Design Mode selection PNGs * fix: preserve Design Mode screenshot orientation * test: verify the Design Mode handoff artifact
* test: cover inactive-space display reconciliation * fix: preserve inactive Spaces during display changes
…ger recompiles cmux-remote cmux-remote's build script hashed the dirty source tree into CMUX_TUI_BUILD_IDENTITY, so every edit in the workspace reran it and recompiled cmux-remote and every crate above it (about 8 s of a 12.6 s one-line edit loop on rbx). The same build script now lives in cmux-build-identity, which only cmux-tui reads; cmux-remote takes the identity as an argument (SshBootstrapConfig::defaults, pinned_linux_payload). The identity value, its inputs and format, and every skew and SSH re-bootstrap comparison are unchanged.
…enum (no bead) (#20887) The package conventions lint fails every PR on the new all-static public enum WindowFrameStrip (cx-me3xd). The report reads its window, so it is an extension on NSWindow; same body, same report. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The harness command builder moved to agent/command.rs on base; the caller token env (caller_env) moves with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Not verified: macOS jobs did not run: compile and app tests were skipped on CI passes on Written by |
- regenerated plans/cmux-next/action-surfaces.json - regenerated plans/cmux-next/actions.md Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38688097375
Split planThis PR isn't a mega-PR. The 803-file count is measured against
None of it is on
Recommendation: don't close it. Resolve those 5 conflicts and land it into
Shared files: #20549 also edits Slices opened
Follow-up slice (not coded yet)
Separate from this PR (needs its own bead): since #21397, a peer picks its own 🤖 Generated with Claude Code |
|
Slices opened, both targeting
Together they cover all of this PR's own change. The 🤖 Generated with Claude Code |
#21642) A peer's `_acpmux/permission_pending` now reaches unix-socket and app watchers of this daemon that did not attach the session (`via: "peer"`), with the peer's decision nonce and run, so the app can show and answer it. Answers still go to the peer, which counts an allow only with its own person's proof. It is relayed only for a session that peer listed as its own and that is not this daemon's, in the run the peer named at its handshake, which is neither this daemon's run nor one another peer named. It never goes to a Web client or on to another peer daemon. The residual (a peer naming a third daemon's run) is in the `hub/person.rs` threat model. Carved from #20653. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… preset
A `session/new` that asks for a mode that doesn't ask, without the
person, now has two ways through:
- Inherit: a caller that is itself an acpmux session in that exact mode on
the same harness family. Each harness gets ACPMUX_SESSION_TOKEN, an HMAC
of its session id under a per-run in-memory secret, and
ACPMUX_SESSION_TOKEN_SOCKET. The token is read only from a unix socket
connection's first `initialize`. The caller's mode is read at request
time.
- Approved preset: `_acpmux/presets {name, approveMode: true}` from the
person records the sha256 of the preset's contents. The approval counts
only while the resolve and the spawn both used those exact contents, for
the preset's own mode, with no request overrides other than a folder.
A start mode never applies to an adopt without the person, and a proven
caller session that asks never gets the full-access default for its
chats. The refusal names where to approve (cmux NEXT on this host, or the
preset to approve there once), with {reason, host, mode, preset} in
error.data. Web and peer clients never get the host name.
person_auto_start.rs runs in the hermetic test environment, and its
person-key pipe is close-on-exec so parallel tests' daemons never hold
each other's key pipe open.
Carved from #20653.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(acpmux): start a no-ask mode from a session in it or an approved preset
A `session/new` that asks for a mode that doesn't ask, without the
person, now has two ways through:
- Inherit: a caller that is itself an acpmux session in that exact mode on
the same harness family. Each harness gets ACPMUX_SESSION_TOKEN, an HMAC
of its session id under a per-run in-memory secret, and
ACPMUX_SESSION_TOKEN_SOCKET. The token is read only from a unix socket
connection's first `initialize`. The caller's mode is read at request
time.
- Approved preset: `_acpmux/presets {name, approveMode: true}` from the
person records the sha256 of the preset's contents. The approval counts
only while the resolve and the spawn both used those exact contents, for
the preset's own mode, with no request overrides other than a folder.
A start mode never applies to an adopt without the person, and a proven
caller session that asks never gets the full-access default for its
chats. The refusal names where to approve (cmux NEXT on this host, or the
preset to approve there once), with {reason, host, mode, preset} in
error.data. Web and peer clients never get the host name.
person_auto_start.rs runs in the hermetic test environment, and its
person-key pipe is close-on-exec so parallel tests' daemons never hold
each other's key pipe open.
Carved from #20653.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(acpmux): an approved preset with a route, and env that names the caller token
Red on 116ab01:
- an approved preset plus a request `route` passes the start gate and
reaches the handler ("no route"), where it should need the person;
- a harness profile env that sets ACPMUX_SESSION_TOKEN reaches the agent
in place of acpmux's caller token.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(acpmux): a route counts as an override, env never sets the caller token
Review fixes for the start gate:
- A request `route` on an approved preset now needs the person, like its
own model, effort, policy or env: a route swaps the provider base URL
and keys.
- A profile's, preset's or request's env no longer replaces
ACPMUX_SESSION_TOKEN or ACPMUX_SESSION_TOKEN_SOCKET. That env is
layered after `caller_env`, so the spawn drops both keys from it.
- The CLI's daemon readiness pipe is close-on-exec from creation
(`std::io::pipe`, pipe2(O_CLOEXEC) where the OS has it) instead of a
`pipe` then `fcntl`, which raced with a fork on another thread.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* cmux NEXT: a folded long thought reads "Thought for 12s" A long thought folded once done (#20948, Debug toggle agentPane.debug.foldLongThoughts) now reads how long the agent thought, from its first chunk to its latest, clamped at zero for out-of-order events. An undated thought, or one under a second, reads "Thought". #20948's "Thought" plus first line stays as the Debug choice agentPane.debug.thoughtHeader = preview, which the app sets on <html> as data-thought-header. New string tools.thoughtFor in all 21 pane locales (split from #20963). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux NEXT perf-hitch2: an evicted terminal shows its last frame at once when shown again (GhosttyNextKit a19c884fa, cx-8w027.19.1) A terminal the warm set evicted showed an empty pane until its new session attached and drew. Ghostty emptied every released drawable (kIOSurfacePurgeableEmpty in IOSurface.deinit), and a hidden surface releases its swap chain, so even the layer's presented frame was blank (ghostty-next PR 42: a plain release; the last owner frees the pages). - A paused surface marks its presented frame volatile and makes it non-volatile before it shows again; a purged one leaves the layer empty until Ghostty's next frame, as before. - Eviction keeps the presented IOSurface (no copy, volatile), at most the warm set terminal capacity, oldest first; a frame dropped by the bound leaves a 480 px hover picture; memory pressure drops them with no renders. - The new session shows it over its first surface (TerminalFrameHold begin(lastFrame:)) until its first content is parsed and drawn, at most 3 s; a purged frame or a hidden mount shows nothing. Hover cards picture it on demand. - debug.surfaces: last_frames outcomes, per pane last_frame_holds, frame_held. Proof: scripts/cmux-next/evicted-terminal-frame-live.py. Pins GhosttyNextKit release xcframework-a19c884fa8d132378ae838dcba95654b177b6723-apple-v6 (checksum 94ff3178... from SHA256SUMS, matching the downloaded zip) and moves the ghostty-next gitlink, package-notices.json and vt-link-graph.json to it (only pkg/macos/iosurface/iosurface.zig changed since e6a27a1fb). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * perf(cmux-next): a terminal mounted hidden after an eviction shows its last frame at its reveal (cx-8w027.19.1) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * perf(cmux-next): hover pictures reclaim a paused surface's volatile frame for the read; a frame in flight at the pause is not reclaimed; no last frame at capacity 0 (cx-8w027.19.1, review) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * perf(cmux-next): last-frame logic lives in CmuxNextTerminal (TerminalLastFrames evict/show/report); the frame shows at the reveal; CmuxNextApp does not grow (cx-8w027.19.1) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * release-notes(next): fit browser highlights to the What's New limits (#21716) * release-notes(next): fit browser highlights to the What's New limits Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * release-notes(next): live links, dev-build wording for remote tabs Replaces cmux.com/whats-new (404) with real try-it actions, says the remote browser tab entries are development-build features (their action is DEV/Nightly only), and drops 'instant' from the tab-switch entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * release-notes(next): fit app highlights to the What's New limits (#21717) * release-notes(next): fit app highlights to the What's New limits Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * release-notes(next): live links and exact profile-menu wording Replaces two 404 links (cmux.com/whats-new, docs/agent-integrations) with real actions or docs pages, and says what the profile button opens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * feat(next): workspace name source and revision, like tab names (hq#1957) (#21648) * feat(next): workspace name source and revision, like tab names (hq#1957) Workspaces now record whether their name came from a person or from auto-naming, the way tab names do (NameSource { User, Auto } and a name_revision), and the daemon decides whether an automatic rename lands. - RegistryWorkspace and the mux Workspace carry name_source and name_revision, stored in two additive workspaces columns (legacy rows read user/0). Both commit paths re-check the stored row's name, source and revision inside the transaction. - rename-workspace and workspace.rename accept source, expected_generation, expected_name_revision and release. An automatic rename never replaces a person's name (operation.failed, reason_code name_pinned), never empties one (name_empty), and loses to any rename after the revision it read (revision.conflict). source auto with release hands a pinned name back. - Workspace projections (raw tree, list-workspaces, resource extra) expose name_source and name_revision; the daemon advertises workspace-name-source-v1. - CLI: cmux workspace rename --source auto --expected-generation G --expected-name-revision N, and --release-to-auto. - Spec, regenerated SDK bindings, cmux-app.d.ts and resource descriptor hashes follow; optchat-chief's RenameWorkspaceRequest literal gains the new fields. - The app's DaemonCapabilities moves workspace-name-source-v1 from unservedByBundledDaemon to optional, since the bundled daemon now serves it. Carved from #20710 onto current feat-cmux-next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(next): project workspace name provenance like the journal states it (hq#1957) feat-cmux-next tests that landed after #20710's base failed on this slice: - batch_close and resource_effects: the journal decorates every stored workspace with extra.name_source/name_revision, but the projected workspace values had no extra, so the public fold never matched and every commit restated every workspace. Every workspace value builder (deltas, full and scoped projection, normalized registry deltas, resource snapshots) now carries the same extra. - key_close_cannot_rebind_a_live_durable_workspace_identity: creating a workspace at a key bound to another live workspace hit the name fence (revision.conflict) before the identity check. The name fence now applies only to the same workspace (numeric id), and the resource store checks identity before the name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(next): workspace_resource_upsert lives with workspace renames (hq#1957) Its name provenance parameter pushed mux.rs past the 1000-line god-file budget (check-no-godfiles.sh); the helper moves to mux/workspace_rename.rs unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(cmux-next): restore #21369 and #21384, undone by the Chief Dock squash (#21690) * test(cmux-next): older history is asked for once per page Fails on feat-cmux-next: a streaming commit near the top asks for the same older page again, because 50a0c60 (#21194) restored the effect #21369 had keyed on the first row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(cmux-next): avoid duplicate older-history loads (#21369) (cherry picked from commit 8f6f9cb) * perf(cmux-next): gate find highlight repaints by transcript changes (#21384) * perf(cmux-next): gate find highlight repaints by transcript changes * fix(cmux-next): preserve find repaint triggers after fold reveals * style(acpmux): format find highlight effect * test(next): carry apps empty-state selector through PR 21384 * test(next): carry apps empty-state selector through find highlight PR (cherry picked from commit afec4de) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * acpmux: no-ask start modes by inherit or an approved preset (#21666) * feat(acpmux): start a no-ask mode from a session in it or an approved preset A `session/new` that asks for a mode that doesn't ask, without the person, now has two ways through: - Inherit: a caller that is itself an acpmux session in that exact mode on the same harness family. Each harness gets ACPMUX_SESSION_TOKEN, an HMAC of its session id under a per-run in-memory secret, and ACPMUX_SESSION_TOKEN_SOCKET. The token is read only from a unix socket connection's first `initialize`. The caller's mode is read at request time. - Approved preset: `_acpmux/presets {name, approveMode: true}` from the person records the sha256 of the preset's contents. The approval counts only while the resolve and the spawn both used those exact contents, for the preset's own mode, with no request overrides other than a folder. A start mode never applies to an adopt without the person, and a proven caller session that asks never gets the full-access default for its chats. The refusal names where to approve (cmux NEXT on this host, or the preset to approve there once), with {reason, host, mode, preset} in error.data. Web and peer clients never get the host name. person_auto_start.rs runs in the hermetic test environment, and its person-key pipe is close-on-exec so parallel tests' daemons never hold each other's key pipe open. Carved from #20653. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(acpmux): an approved preset with a route, and env that names the caller token Red on 116ab01: - an approved preset plus a request `route` passes the start gate and reaches the handler ("no route"), where it should need the person; - a harness profile env that sets ACPMUX_SESSION_TOKEN reaches the agent in place of acpmux's caller token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(acpmux): a route counts as an override, env never sets the caller token Review fixes for the start gate: - A request `route` on an approved preset now needs the person, like its own model, effort, policy or env: a route swaps the provider base URL and keys. - A profile's, preset's or request's env no longer replaces ACPMUX_SESSION_TOKEN or ACPMUX_SESSION_TOKEN_SOCKET. That env is layered after `caller_env`, so the spawn drops both keys from it. - The CLI's daemon readiness pipe is close-on-exec from creation (`std::io::pipe`, pipe2(O_CLOEXEC) where the OS has it) instead of a `pipe` then `fcntl`, which raced with a fork on another thread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * cmux-next: the static status look everywhere; indicators name their state (cx-sya4n) (#21664) * test(cmux-next): the static look reaches tabs, the attention ring, the agent pane and every label (cx-sya4n) Red: tabs keep their icon set's marks and colored dots, the attention ring blinks, the agent pane's theme has no statusIndicators look and indicators have no tooltip or VoiceOver label (compile failure). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux-next: the static status look everywhere, and every indicator names its state (cx-sya4n) Tabs draw the static look's still marks (the badge too, never a dot told apart by color alone); the pane attention ring is the steady ring under Static; the agent pane's theme sends statusIndicators (static under Static or Reduce Motion). Every StatusIndicatorView's tooltip and VoiceOver label is the state's short name (StatusIndicatorState .shortLabel, nine strings in every catalog locale), and a tab's needs-input VoiceOver text names the blocked kind. Carved from #20511. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Co-authored-by: safe-push <safe-push@cmux.local>
* cmux-next: keep the Mac awake while agents work (sessions.keepAwake) New Settings > Agents > Agent Sessions row, "Keep Mac Awake While Agents Work": Off, While Plugged In (default) or Always. While a local agent runs a turn (an acpmux session, or a tab's agent hooks or OSC 7501 working record), idle system sleep stays off. The display can still sleep and closing the lid still sleeps the Mac. - schemas/settings/source/sessions.json is the row's source; the Swift descriptor, parser constants and choice enum are generated. - KeepAwakeService holds one ProcessInfo activity for every reason, so the Keep Mac Awake action and the setting never end each other's hold. The action now goes through it and still holds on battery. - PowerSourceMonitor follows IOKit's power-source notification on the main run loop (no polling); unplugging releases a While Plugged In hold. - Agents may not set the key (userOnly). - Strings in all 21 locales of the settings catalog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux NEXT perf-hitch2: an evicted terminal shows its last frame at once when shown again (GhosttyNextKit a19c884fa, cx-8w027.19.1) A terminal the warm set evicted showed an empty pane until its new session attached and drew. Ghostty emptied every released drawable (kIOSurfacePurgeableEmpty in IOSurface.deinit), and a hidden surface releases its swap chain, so even the layer's presented frame was blank (ghostty-next PR 42: a plain release; the last owner frees the pages). - A paused surface marks its presented frame volatile and makes it non-volatile before it shows again; a purged one leaves the layer empty until Ghostty's next frame, as before. - Eviction keeps the presented IOSurface (no copy, volatile), at most the warm set terminal capacity, oldest first; a frame dropped by the bound leaves a 480 px hover picture; memory pressure drops them with no renders. - The new session shows it over its first surface (TerminalFrameHold begin(lastFrame:)) until its first content is parsed and drawn, at most 3 s; a purged frame or a hidden mount shows nothing. Hover cards picture it on demand. - debug.surfaces: last_frames outcomes, per pane last_frame_holds, frame_held. Proof: scripts/cmux-next/evicted-terminal-frame-live.py. Pins GhosttyNextKit release xcframework-a19c884fa8d132378ae838dcba95654b177b6723-apple-v6 (checksum 94ff3178... from SHA256SUMS, matching the downloaded zip) and moves the ghostty-next gitlink, package-notices.json and vt-link-graph.json to it (only pkg/macos/iosurface/iosurface.zig changed since e6a27a1fb). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * cmux-next: regenerate settings exports and defaults for sessions.keepAwake Exports from the fleet export step at 4beacc0 (run-swift-testing-suites.sh, export-settings); defaults snapshot from defaults_guard.py snapshot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(cmux-next): a terminal mounted hidden after an eviction shows its last frame at its reveal (cx-8w027.19.1) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * perf(cmux-next): hover pictures reclaim a paused surface's volatile frame for the read; a frame in flight at the pause is not reclaimed; no last frame at capacity 0 (cx-8w027.19.1, review) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * cmux-next: regenerate settings exports after merging feat-cmux-next Exports from the fleet export step at 011a67c. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * restore-trace.py: trace a relaunch restore per pane with the snapshot sources that moved (cx-8w027.23) * restore-trace.py: every pane in short, 50 ms reads (cx-8w027.23) * perf(cmux-next): last-frame logic lives in CmuxNextTerminal (TerminalLastFrames evict/show/report); the frame shows at the reveal; CmuxNextApp does not grow (cx-8w027.19.1) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * cmux NEXT perf-restore: no pane re-run on connect, room badge from the strip's window, one topology per turn for every pane that changed (cx-8w027.23) - PaneController.Snapshot.connected is gone: apply never read it, and the read re-ran every pane's snapshot when the daemon connected (16 per 16-pane restore). A reconnect with a new daemon still re-maps through generation and surfaces. - BrowserProfileService.roomDefault takes the room of the window the strip or sidebar avatar draws in (WindowState.profileID, observed), so a room's profile badge follows that window; the lookup of WindowManager state (not observed) stays only for one-time choices (a new tab's profile). - A pane whose part of the focus topology changed marks the workspace (setNeedsTopology); FocusCoordinator builds and sends one topology per owner for the whole main-actor turn, at its end or before any state read or event (I4), instead of one per pane. * cmux NEXT: no focus ring on the daemon recovery status label (#21679) #20765 made the not-connected screen's status label selectable without focusRingType = .none, so check:text-focus fails every feat-cmux-next PR. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * FocusCoordinator: state is a pure read; key routing and events reduce the pending topology; the turn-end reduction runs detached; what the settled observer queues runs (review of cx-8w027.23 slice 1) * release-notes(next): fit browser highlights to the What's New limits (#21716) * release-notes(next): fit browser highlights to the What's New limits Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * release-notes(next): live links, dev-build wording for remote tabs Replaces cmux.com/whats-new (404) with real try-it actions, says the remote browser tab entries are development-build features (their action is DEV/Nightly only), and drops 'instant' from the tab-switch entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * release-notes(next): fit app highlights to the What's New limits (#21717) * release-notes(next): fit app highlights to the What's New limits Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * release-notes(next): live links and exact profile-menu wording Replaces two 404 links (cmux.com/whats-new, docs/agent-integrations) with real actions or docs pages, and says what the profile button opens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * feat(next): workspace name source and revision, like tab names (hq#1957) (#21648) * feat(next): workspace name source and revision, like tab names (hq#1957) Workspaces now record whether their name came from a person or from auto-naming, the way tab names do (NameSource { User, Auto } and a name_revision), and the daemon decides whether an automatic rename lands. - RegistryWorkspace and the mux Workspace carry name_source and name_revision, stored in two additive workspaces columns (legacy rows read user/0). Both commit paths re-check the stored row's name, source and revision inside the transaction. - rename-workspace and workspace.rename accept source, expected_generation, expected_name_revision and release. An automatic rename never replaces a person's name (operation.failed, reason_code name_pinned), never empties one (name_empty), and loses to any rename after the revision it read (revision.conflict). source auto with release hands a pinned name back. - Workspace projections (raw tree, list-workspaces, resource extra) expose name_source and name_revision; the daemon advertises workspace-name-source-v1. - CLI: cmux workspace rename --source auto --expected-generation G --expected-name-revision N, and --release-to-auto. - Spec, regenerated SDK bindings, cmux-app.d.ts and resource descriptor hashes follow; optchat-chief's RenameWorkspaceRequest literal gains the new fields. - The app's DaemonCapabilities moves workspace-name-source-v1 from unservedByBundledDaemon to optional, since the bundled daemon now serves it. Carved from #20710 onto current feat-cmux-next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(next): project workspace name provenance like the journal states it (hq#1957) feat-cmux-next tests that landed after #20710's base failed on this slice: - batch_close and resource_effects: the journal decorates every stored workspace with extra.name_source/name_revision, but the projected workspace values had no extra, so the public fold never matched and every commit restated every workspace. Every workspace value builder (deltas, full and scoped projection, normalized registry deltas, resource snapshots) now carries the same extra. - key_close_cannot_rebind_a_live_durable_workspace_identity: creating a workspace at a key bound to another live workspace hit the name fence (revision.conflict) before the identity check. The name fence now applies only to the same workspace (numeric id), and the resource store checks identity before the name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(next): workspace_resource_upsert lives with workspace renames (hq#1957) Its name provenance parameter pushed mux.rs past the 1000-line god-file budget (check-no-godfiles.sh); the helper moves to mux/workspace_rename.rs unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(cmux-next): restore #21369 and #21384, undone by the Chief Dock squash (#21690) * test(cmux-next): older history is asked for once per page Fails on feat-cmux-next: a streaming commit near the top asks for the same older page again, because 50a0c60 (#21194) restored the effect #21369 had keyed on the first row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(cmux-next): avoid duplicate older-history loads (#21369) (cherry picked from commit 8f6f9cb) * perf(cmux-next): gate find highlight repaints by transcript changes (#21384) * perf(cmux-next): gate find highlight repaints by transcript changes * fix(cmux-next): preserve find repaint triggers after fold reveals * style(acpmux): format find highlight effect * test(next): carry apps empty-state selector through PR 21384 * test(next): carry apps empty-state selector through find highlight PR (cherry picked from commit afec4de) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * acpmux: no-ask start modes by inherit or an approved preset (#21666) * feat(acpmux): start a no-ask mode from a session in it or an approved preset A `session/new` that asks for a mode that doesn't ask, without the person, now has two ways through: - Inherit: a caller that is itself an acpmux session in that exact mode on the same harness family. Each harness gets ACPMUX_SESSION_TOKEN, an HMAC of its session id under a per-run in-memory secret, and ACPMUX_SESSION_TOKEN_SOCKET. The token is read only from a unix socket connection's first `initialize`. The caller's mode is read at request time. - Approved preset: `_acpmux/presets {name, approveMode: true}` from the person records the sha256 of the preset's contents. The approval counts only while the resolve and the spawn both used those exact contents, for the preset's own mode, with no request overrides other than a folder. A start mode never applies to an adopt without the person, and a proven caller session that asks never gets the full-access default for its chats. The refusal names where to approve (cmux NEXT on this host, or the preset to approve there once), with {reason, host, mode, preset} in error.data. Web and peer clients never get the host name. person_auto_start.rs runs in the hermetic test environment, and its person-key pipe is close-on-exec so parallel tests' daemons never hold each other's key pipe open. Carved from #20653. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(acpmux): an approved preset with a route, and env that names the caller token Red on 116ab01: - an approved preset plus a request `route` passes the start gate and reaches the handler ("no route"), where it should need the person; - a harness profile env that sets ACPMUX_SESSION_TOKEN reaches the agent in place of acpmux's caller token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(acpmux): a route counts as an override, env never sets the caller token Review fixes for the start gate: - A request `route` on an approved preset now needs the person, like its own model, effort, policy or env: a route swaps the provider base URL and keys. - A profile's, preset's or request's env no longer replaces ACPMUX_SESSION_TOKEN or ACPMUX_SESSION_TOKEN_SOCKET. That env is layered after `caller_env`, so the spawn drops both keys from it. - The CLI's daemon readiness pipe is close-on-exec from creation (`std::io::pipe`, pipe2(O_CLOEXEC) where the OS has it) instead of a `pipe` then `fcntl`, which raced with a fork on another thread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * cmux-next: the static status look everywhere; indicators name their state (cx-sya4n) (#21664) * test(cmux-next): the static look reaches tabs, the attention ring, the agent pane and every label (cx-sya4n) Red: tabs keep their icon set's marks and colored dots, the attention ring blinks, the agent pane's theme has no statusIndicators look and indicators have no tooltip or VoiceOver label (compile failure). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux-next: the static status look everywhere, and every indicator names its state (cx-sya4n) Tabs draw the static look's still marks (the badge too, never a dot told apart by color alone); the pane attention ring is the steady ring under Static; the agent pane's theme sends statusIndicators (static under Static or Reduce Motion). Every StatusIndicatorView's tooltip and VoiceOver label is the state's short name (StatusIndicatorState .shortLabel, nine strings in every catalog locale), and a tab's needs-input VoiceOver text names the blocked kind. Carved from #20511. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(cmux-next): restore the URL chip hover card styles lost in the Chief Dock squash (#21699) * test(cmux-next): every URL chip hover card class has a rule Fails on feat-cmux-next: 50a0c60 (#21194) dropped the .cv-link-preview rules #21199 added, while LinkChips.tsx still renders those classes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cmux-next): restore the URL chip hover card styles (#21199) 50a0c60 (#21194, a stale-base squash) removed the nine .cv-link-preview rules while LinkChips.tsx still renders them, so the card drew unstyled. Restored byte for byte from 5106cc9. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * acpmux: a saved model or effort pick reaches open New Tab pages (#21677) * test(acpmux): a saved model or effort pick is announced to watchers A New Tab prewarmed before a pick draws the old start effort and the chat then starts on the pick. Fails: note_pick saves the new start default but sends no _acpmux/harnesses_changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(acpmux): announce a saved model or effort pick to watchers note_pick makes the pick the harness's start in _acpmux/harnesses, but told nobody, so a New Tab prewarmed before the pick kept drawing the old start effort until the chat started on the pick. It now sends _acpmux/harnesses_changed, as a saved mode pick already announces start_mode_changed; pages re-read the catalog on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * cmux-next: keep awake lives in its own target (CmuxNextKeepAwake) Review of #21730: CmuxNextApp only shrinks (check-app-size.sh), so KeepAwakePolicy, PowerSourceMonitor and KeepAwakeService move to a new CmuxNextKeepAwake target with their tests. The App keeps a one-line start and the Keep Mac Awake binding; its source is 10 lines under the base. The power-source callback now hops to the main actor with a Task instead of MainActor.assumeIsolated (a banned crash class). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux-next: route test knows the keep-awake tests; one policy write per change Review of #21730: a CmuxNextSidebar change now also selects CmuxNextKeepAwakeTests (CmuxNextKeepAwake depends on CmuxNextBridge), so test_a_shared_module_selects_its_dependents lists it. KeepAwakeService sets the setting and the agents-working fact in one policy write, so the hold cannot drop between the two. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Co-authored-by: safe-push <safe-push@cmux.local>
acpmux: approval paths for no-ask start modes that don't need the person each time, plus pending prompts that reach peered apps.
When a script or agent asked acpmux for a mode that doesn't ask, the request needed the person's approval. That approval was only visible in cmux NEXT on the same host, and the error didn't say where to give it. The threat model is written at the top of
hub/person.rs.ACPMUX_SESSION_TOKEN, an HMAC of the session id keyed by a secret made at each daemon start and kept only in memory.initialize, and only over the unix socket._acpmux/presets {name, approveMode: true}from the person records a sha256 of the preset's contents._acpmux/permission_pendingreaches this daemon's watchers that did not attach the session (via: "peer").error.datacarries{reason, host, mode, preset}so an app can show localized text.Review fixes
From the security review:
session/newthat adopts, now needs the person. The request is refused before anything starts, because an adopt may find a session that is already running (ifLive: "open"), and only the person widens a running session._acpmux/permission_pendingis relayed only when all of these hold:It goes only to unix-socket and app watchers, never to a Web client and never on to another peer daemon.
session/newreads the preset's contents at three points: the request check; the resolve of model, effort and env; and the spawn of args, env and system prompt.harnessstring, not the profile that name resolves to.cwd) is fine.modeApproval. Documented: it is an unkeyed sha256, so same-uid writes to config.json are out of scope.error.data.host.ACPMUX_SESSION_TOKEN_SOCKET. The CLI sends the caller token only when it connects to that socket.Out of scope, and pre-existing on the base: a peer can send
session/updatefor a local session id. Tracked as bead cx-r492l.Second review
spawn_lock, so a request on the session while an approved preset was starting could overwrite it.ensure_childnow records the digest it spawned with, underspawn_lock, and only when it starts a new harness. A live or re-adopted child never writes it.person.rsand below in open questions. Tracked as bead cx-i4rte.ACPMUX_SOCKETis made absolute, so a relative path cannot point the CLI and the daemon at different sockets.MODE_FIELDS), not onlymodeId.Open questions for review
/proc/<pid>/environ,ps -E). It grants only that session's current mode, which its own agent can already use. Binding the token to the connecting process's ancestry is later hardening.personInstanceof another daemon the app enrolled directly. The app must refuse to prove avia: "peer"prompt whosepersonInstanceis a daemon it enrolled directly. This needs an app change (bead cx-i4rte).Not in this PR
Tests
The tests are at the daemon boundary:
tests/person_auto_start.rs(auto_start_*) and a peer test intests/peer_integration.rsfor the pending prompt (c).auto_start_):left: "full-access",right: "normal". A chat started by an agent in an asking mode came up in full access.auto_start_,peer,start_modeandpreset.auto_start_, red on Linux: 3 failed, 9 passed. An adopt moved a running session to bypass, a request model override started without the person, and a respawned edited preset came back in bypass.peer_pending, red on Linux: 3 failed, 1 passed (the legit relay). A forged prompt (local session, this daemon's run, unknown session) reached the watcher.auto_start_: 12 passed (run 38592681245).peer: 75 passed (run 38600221583).preset: 26 passed (run 38601708940).auto_start_a_request_during_the_start_never_rebinds_the_approval, red on Linux (run 38611185844). The start was refused withpermission.person_required.auto_start_: 13 passed (run 38618842274).peer: 75 passed (run 38626137238).preset: 26 passed (run 38635193026).cmux-tui/tests/replace_unresponsive_owner.rs(unused qualification), which this PR does not touch.auto_start_: Linux 19 passed, macOS 44 passed (run 38640660645).peer: Linux 77 passed, macOS 101 passed (run 38643120684).preset: Linux 26 passed (Linux job rerun after a cancel), macOS 51 passed (run 38646576474).Changelog
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.Summary by cubic
Adds approval paths for starting acpmux sessions in a no-ask mode without the person, relays pending approval prompts to peered apps, and keeps the motion system and Chief work the base merge brought in.
initialize) can start sessions in its own current no-ask mode on the same harness family; claims in request params are ignored. The CLI sends its caller token only to the socket of the issuing daemon (ACPMUX_SESSION_TOKEN_SOCKET), andACPMUX_SOCKETis made absolute.host,modeandpresetinerror.data.Security boundaries
Rode in from the base merge
MotionClockper window,ui.animations.newTab(on by default) andui.animations.newWorkspace, bounce and Reduce Motion switches; a new tab grows in at its slot on thetabOpenspring, structural changes snap under Reduce Motion. Palette rows for "Window Style" preview the style live while highlighted.chief.profile.adoptmakes an existing agent participant a Chief record under its own id (refuses a bound participant, revokes its earlier credentials); a brain started by the supervisor gets the account's home; every Chief profile page call answers within a deadline on a relay connection to the current owner.labs.classicHomekeeps classic Home one release; the agent CLI card'sharness.update/harness.restartrun on this Mac's daemon with a gesture.Written for commit 7f2b6a3. Summary will update on new commits.