10706 Commits
Author SHA1 Message Date
Lawrence Chen 3fad5abf1d Merge pull request #10424 from manaflow-ai/feat/coderouter-status-session-counts
Expose per-account session counts and cooldowns in account listing
2026-08-19 04:13:14 -07:00
Lawrence Chen 34f0e1eeca Expose per-account session counts and cooldowns in account listing
The status endpoint now reports, per account, how many sessions are
bound with traffic inside the routing load window, plus the account's
cooldown deadline. The coderouter CLI uses both to make sticky routing
and rate-limit cooldowns visible. The session-count read is display
only and fails to zero so it can never take the status endpoint down.
2026-08-19 03:59:50 -07:00
Lawrence Chen 882ab10e75 Merge pull request #10402 from manaflow-ai/feat/coderouter-free-tier
Make hosted coderouter free for up to 3 accounts
nightly
2026-08-18 23:44:59 -07:00
Lawrence Chen 9ad361ed37 Make hosted coderouter free for up to 3 accounts
Pricing change: a team may connect and route up to 3 provider
accounts (subscriptions) for free. More than 3 requires an active
cmux Pro or Team subscription.

- New services/coderouter/entitlement.ts: free tier checked first
  (one indexed count read); the Stripe read runs only for teams over
  the limit. Both checks fail closed.
- Session issuance: replaces the flat Pro gate. Over-limit teams
  without a subscription get 402 with the count in the message.
- Account add: connecting an account beyond the limit without a
  subscription returns 402 before anything is stored. Re-importing
  an account the team already has is always allowed, so a broken
  account stays repairable on the free tier. The accounts POST
  handler is now factory-built for dependency-injected tests.
- Billing-lapse token revocation stays unchanged: after a lapse the
  CLI renews and the free tier re-qualifies teams with <= 3 accounts.
- New analytics: entitlement_basis on route_session_issued and a
  coderouter_account_limit_reached event, both schema-whitelisted.
- CODEROUTER_HOSTED_PRO_REQUIRED=0 still disables all gating.

Boundary: exactly 3 accounts is free; the 4th needs Pro/Team.
2026-08-18 23:21:33 -07:00
Lawrence Chen ae7d761e03 Merge pull request #10387 from manaflow-ai/fix/coderouter-codex-session-spread
Spread Codex placements and pin sessions to accounts
2026-08-18 21:57:20 -07:00
Austin Wang c15d665f65 Merge pull request #10363 from manaflow-ai/issue-10103-copy-mode-selection-bleed
Fix terminal Copy Mode selection scope across split panes
2026-08-18 21:46:09 -07:00
Lawrence Chen 425f2d7efe Keep sticky sessions through in-flight credential refreshes
A bound account in state 'refreshing' is healthy; its refresh lease
resolves in seconds. Treat it as usable for sticky reuse, and give a
sticky session a short bounded wait (4 x 500ms) when the refresh lease
is busy, instead of instantly moving the session and discarding its
prompt cache. Non-sticky requests keep the fail-fast move.
2026-08-18 21:43:42 -07:00
Lawrence Chen 7d79a858f6 Degrade to legacy routing while the session table migration is pending
If the code deploys before the additive migration is applied, the
session-stickiness statements hit undefined_table (42P01). Treat that
as: no binding found, claim without the session-load ordering term, and
skip the pin. Routing behaves exactly like the pre-change code until
the migration lands, instead of erroring every Codex request. Covered
by a DB behavior test that renames the table away and back.
2026-08-18 21:38:20 -07:00
Lawrence Chen 18ed976a1b Spread Codex placements and pin sessions to accounts
Port of subrouter PR #228 to the coderouter TypeScript data plane.

Problems in the current routing:
- selectAccountForRequest picks an account per request with three
  independent statements (sweep, select, update). Nothing spans the
  read-pick-write sequence, so parallel session starts read the same
  snapshot and herd onto one account. That account burns quota first,
  then every session reroutes to the next account at once.
- No session stickiness exists. Each request rotates to the
  least-recently-used account, so the provider prompt cache is
  discarded on nearly every turn and the whole prefix is re-billed
  as uncached input across all accounts.

Fix:
- New coderouter_session_accounts table pins one agent session (the
  Codex CLI session_id header) to one account.
- selectAccountForSession honors a usable binding first (sticky), and
  moves a session only when its account is broken, cooling down,
  removed, or already attempted in this request.
- New placements claim an account atomically in one statement with
  FOR UPDATE SKIP LOCKED, ordered by fewest recently-active bound
  sessions, then least-recently-used. Concurrent claims take
  different accounts instead of the same snapshot argmax. When every
  candidate row is locked, a blocking fallback claim accepts a
  collision instead of failing the request.
- selectAccountForRequest (models, opencode) now uses the same atomic
  claim, which closes its read-pick-write race too.

Tests: unit tests for the selector and the responses proxy, and
CMUX_DB_TEST-gated behavior tests that prove sequential spread,
concurrent non-herding, stickiness, move-on-cooldown, exclusion,
and last-write-wins binding against real Postgres.
2026-08-18 21:34:14 -07:00
Abdulaziz Albahar 9d815638c7 Merge pull request #10381 from manaflow-ai/fix-legacy-beta-namespace-fallback
fix(iroh): let pre-namespace legacy iOS bindings pair with default and nightly Macs
2026-08-18 20:59:38 -07:00
Lawrence Chen 34483ba969 Merge pull request #10382 from manaflow-ai/chore-testbox-skill-moved
chore: move the Testbox skill to cmuxterm-hq
cmux-tui-v0.10.1
2026-08-18 20:43:39 -07:00
Lawrence Chen bc0dcaa8b9 chore: move the Testbox skill to cmuxterm-hq
The lane is fleet infrastructure we operate, not contributor guidance for this
codebase, so the prose now lives in cmuxterm-hq at
skills/infra/blacksmith-testbox/ beside macfleet and cloud-vm-ops. See
manaflow-ai/cmuxterm-hq#306.

Everything the box actually executes stays here: both workflows, the five
scripts/blacksmith-*.sh helpers, and the two guards that validate them. Only
tests/test_testbox_doc_blocks.sh follows the skill, because it validates that
prose, and hq gained a workflow so it keeps gating rather than quietly stopping.

CLAUDE.md keeps a pointer rather than dropping the subject, because an agent
starting cmux-tui Rust work reads this file and must still learn not to compile
on the Mac. Worth stating plainly: all nine trial agents found this skill from a
local file in this repo, and a pointer to another checkout is weaker
discoverability, particularly from a plain cmux clone.
2026-08-18 20:40:08 -07:00
Abdulaziz Albahar 540bf2b9e4 Merge pull request #10310 from manaflow-ai/feat-conn-method-diagnostics
feat(ios): state connection method and live transport in diagnostics reports
2026-08-18 20:29:53 -07:00
Abdulaziz Albahar 2048eebfc9 Merge pull request #10294 from manaflow-ai/feat-tsonly-secondary-gate
fix(ios): Tailscale-only must gate secondary-Mac and discovery Iroh dials
2026-08-18 20:29:44 -07:00
Abdulaziz Albahar 65d4870c00 Merge pull request #10313 from manaflow-ai/fix-gallery-picker-simstream
fix(ios): pass selectSimulatorStream in the surface gallery preview
2026-08-18 20:29:39 -07:00
Abdulaziz AlbaharandClaude Fable 5 3f2b88f307 refactor(ios): map connection method to its diagnostics enum exhaustively
Review follow-up: replace the 0/1 ternaries with an exhaustive switch into
DiagnosticConnectionMethod so a future third method becomes a compile error
instead of silently reporting as Tailscale.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-18 20:29:20 -07:00
Abdulaziz AlbaharandClaude Fable 5 58416dcbd4 test(ios): pin the runtime clock in the Tailscale-only pool tests
Review follow-up: inject a fixed date instead of wall-clock Date() so
route-selection behavior in these tests is deterministic.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-18 20:26:47 -07:00
Lawrence Chen bee0e6c546 Merge pull request #10304 from manaflow-ai/feat-tab-identity-single-owner
Make the topology own tab identity
2026-08-18 20:25:19 -07:00
Lawrence Chen 76418e4592 Merge pull request #10299 from manaflow-ai/feat-startup-orphan-terminal-projection
Fix startup wedge when several terminal hosts are unadoptable
2026-08-18 20:25:01 -07:00
Abdulaziz AlbaharandClaude Fable 5 5bc3fee2c8 fix(iroh): let pre-namespace legacy iOS bindings pair with default and nightly Macs
The bundle-isolation rollout (#9183) records iOS builds without the
X-Cmux-App-Namespace header as legacy. Those are the shipped pre-namespace
Beta binaries on the default lane, and they lost the official-namespace
default->{default,nightly} exception: discovery still listed a Nightly Mac,
but issuePairGrant denied it with target_not_pairable, surfacing on the
phone as Authorization failed while relay policy and reachability passed.

Legacy callers on the default lane now get the same default+nightly Mac
reach as official namespaced apps, applied through the single
canIOSBindingUseMac choke point so pair grants and proofed discovery agree.
Non-default legacy lanes keep exact tag matching, and canIOSBindingForgetMac
is split off the alias so the destructive forget path is not broadened.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-18 20:15:24 -07:00
Abdulaziz AlbaharandClaude Fable 5 d856159ce8 test(iroh): legacy default-lane iOS bindings must reach default and nightly Macs
Old Beta builds predate the X-Cmux-App-Namespace header and binding request
proofs, so the broker records them as legacy/default. They currently lose the
official-namespace default->nightly exception and get target_not_pairable
against Nightly Macs. These tests pin the intended compatibility: pairing and
discovery gain the fallback, non-default legacy lanes keep exact matching, and
the destructive forget_mac path is not broadened.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-18 20:08:29 -07:00
Austin Wang 11684e5064 Merge pull request #10359 from manaflow-ai/issue-10102-drop-ghosted-regression
Fix terminal file drops being ghosted after pane teardown
2026-08-18 18:12:07 -07:00
austinpower1258 485d627042 fix: complete drop result and isolate fixtures 2026-08-18 16:43:22 -07:00
austinpower1258 d4a2021b1f test: cover multiple promised image items 2026-08-18 16:40:01 -07:00
austinpower1258 94f35f8e5a fix: read each promised drop item 2026-08-18 16:39:08 -07:00
austinpower1258 b7d1c230e2 fix: handle optional drag pasteboard types 2026-08-18 16:35:24 -07:00
austinpower1258 5129950f7e fix: make transient image drops durable 2026-08-18 16:34:02 -07:00
austinpower1258 b372e09f9f fix: avoid redundant pasteboard type fallbacks 2026-08-18 15:04:32 -07:00
austinpower1258 167eb677e6 fix: expose pasteboard temporary root to drop planner 2026-08-18 14:57:53 -07:00
austinpower1258 c8f202201b fix: preserve promised image drop files 2026-08-18 14:19:45 -07:00
austinpower1258 6e56e6ff14 fix: cancel copy mode when pane deactivates 2026-08-18 14:16:33 -07:00
austinpower1258 58a0400a6e fix: recognize unix temporary drop paths 2026-08-18 14:14:07 -07:00
austinpower1258 a1a5ef6c39 fix: restore terminal file drops and retain transient images 2026-08-18 14:12:16 -07:00
austinpower1258 b0948c0341 fix: cancel copy mode on responder loss 2026-08-18 14:11:59 -07:00
austinpower1258 d3e3abd367 fix: scope terminal copy mode to focused pane 2026-08-18 14:01:01 -07:00
austinpower1258 9f556f118e test: require copy mode to end on focus loss 2026-08-18 14:00:14 -07:00
Abdulaziz Albahar 786a35d099 Show model loading and model-specific effort controls (#10098)
* test: cover pending model catalogs in shared picker

* Show model catalog loading in shared picker

* test: expose buried model loading state

* Make pending model picker unmistakable

* test: require model-specific effort picker

* Use model-specific effort metadata

* test: require native model effort picker

* test: isolate native effort picker regression

* test: isolate iOS UI test plan

* feat: add native model effort picker

* test: verify native effort choices

* test: cover all native effort choices

* test: require shared readable picker scroller

* fix: keep task pickers readable in one scroller

* test: measure visible picker labels accurately

* test: cover default model effort picker

* fix: source efforts for the default model

* fix: keep default efforts through draft restore

* test: cover unpinned default model efforts

* test: define semantic OpenCode effort ordering

* fix: order OpenCode effort variants semantically

* test: cover task model discovery failures

* fix: surface task model discovery errors

* fix: keep model discovery errors readable

* fix: resolve provider loading review findings
2026-08-18 13:58:00 -07:00
austinpower1258 c7e3d94b66 test: prevent inactive terminal drop target shadowing 2026-08-18 13:50:10 -07:00
Abdulaziz AlbaharandClaude Fable 5 7589f52b81 Isolate every iOS build by bundle identity (#9183)
* test: require iOS build isolation

* fix: isolate iOS builds by bundle identity

* fix: compile namespaced Mac backup publisher

* chore: remove shared app group example

* test: require isolated iOS OAuth cookies

* fix: isolate iOS OAuth browser cookies

* fix: close autoreview namespace gaps

* fix: complete iOS namespace isolation

* refactor: satisfy namespace isolation policy

* fix: close namespace migration review gaps

* fix: preserve isolated Iroh and OAuth admission

* test: cover isolated iOS rollout paths

* fix: authenticate isolated iOS rollout paths

* test: cover authenticated management recovery

* fix: authorize isolated management operations

* test: cover cached proof and Mac forget

* fix: preserve proof across isolated lifecycle paths

* fix: satisfy Iroh API package policy

* test: cover autoreview isolation regressions

* fix: close isolated rollout review gaps

* test: cover target and trust boundary regressions

* fix: enforce isolated pairing boundaries

* test: cover legacy tombstone migration

* fix: preserve tombstones across backup migration

* test: cover migration precedence and discovery privacy

* fix: enforce migration and discovery precedence

* test: cover push targeting and migration bounds

* fix: close push and migration isolation gaps

* test: prevent entitlement dumps on signing failures

* fix: redact signed entitlements on upload failure

* fix: make backup scope provider sendable

* fix: return localized pairing target names

* fix: drain legacy namespaced revocations

* test: track push token policy limits

* test: cover post-revocation fallback paths

* fix: refresh authority after binding revocations

* test: cover paired-Mac migration boundaries

* fix: bound and scope legacy paired-Mac migration

* fix: capture migration account without async coalescing

* fix: keep legacy migration and keychain deletion safe

* chore: refresh pull request head

* fix: bound legacy backup reconciliation

* test: cover conditional paired Mac migration

* fix: make paired Mac migration conditional

* test: cover pairing migration recovery races

* fix: serialize identity persistence recovery

* test: cover migration team and pairing lanes

* fix: pin migration team and pairing channel

* test: await development identity storage

* test: cover pairing emission and keychain scope

* fix: fail closed on app and keychain scope

* test: cover bundle-derived Mac namespaces

* fix: namespace Mac bindings by bundle

* test: cover legacy sign-out binding namespaces

* fix: revoke bindings in their stored namespace

* test: expect APNs bundle in send outcome

* test: cover untagged debug pairing identity

* fix: preserve untagged debug pairing identity

* test: cover rate-limited pending revocations

* fix: drain revocations after rate-limited registration

* test: cover safe pending revocation reconciliation

* fix: preserve active binding during revocation recovery

* fix: preserve broker proof through client wrappers

* test: retain authorization during rate-limited recovery

* fix: preserve retained binding during rate-limit recovery

* test: cover stale binding cleanup authorization

* fix: authorize stale binding cleanup

* docs: document binding authorization helpers

* test: type stale cleanup response

* fix(macos): restore compilable cleanupSurfaceState call

https://github.com/manaflow-ai/cmux/pull/10072 changed this call site to
pass workspaceID: but never landed that overload, so the macOS target
has not compiled since it merged (CI is dispatch-only and did not catch
it). Restore the existing signature; the native-mobile-surface
preservation intent needs to re-land together with its implementation.

Co-Authored-By: Claude Fable 5 <[email protected]>

* fix: wire mobile surface artifact integration

* fix: require explicit trust broker namespace

* fix: require explicit management revocation routes

* fix: reject ambiguous iOS bundle namespaces

* fix: bound identity waits and scope legacy auth

* fix: cancel queued identity operations

* fix: modernize browser change handlers

* fix: scope legacy token deletion

---------

Co-authored-by: Claude Fable 5 <[email protected]>
2026-08-18 13:37:30 -07:00
Abdulaziz Albahar 029d652972 docs: define shared fleet capacity (#10312)
* docs: define shared fleet and messaging policy

* docs: keep messaging policy global
2026-08-18 12:34:01 -07:00
Lawrence Chen c821be8dfc Merge pull request #10306 from manaflow-ai/fix-testbox-push-requirement
fix: require a pushed commit for Testbox benchmarks, and lead the skill with the workflow
2026-08-17 23:31:24 -07:00
Lawrence Chen 0c18281d4e fix: the demo script leaked the runner it told you to worry about
Revalidating the demo after the set-difference rewrite passed the build (2m13s
then 0.14s, box stopped, exit 0), but the org check afterwards showed its warmup
run still in_progress, holding a 32 vCPU runner. The skill calls cancelling a
required step and the script did not do it, so the one entry point most people
will run was the one leaking.

It records the run it approves and cancels it from the same EXIT trap that stops
the box, so a Ctrl-C cleans up both.
2026-08-17 23:24:28 -07:00
Lawrence Chen 8dca3436f8 fix: cargo test needs umask 022 on a Testbox, and I shipped it without
I added the cargo test line to the skill without running it. A concurrency
trial ran it and it fails on a fresh box: 104 failures, exit 101, all one root
cause. `blacksmith testbox run` gives a shell at umask 0002, so test directories
are created group-writable, and cmux-remote's secure-directory check correctly
rejects an ancestor writable by other users without the sticky bit.

Hosted CI runs at umask 022, so the suite passes there and fails here. The suite
is not umask-independent, which means this lane does not reproduce CI unless the
command says so. With `umask 022` prepended, 3504 tests pass in about 88s.
Builds and clippy are unaffected.
2026-08-17 23:17:11 -07:00
Lawrence Chen 39410cd265 fix: identify your run by set difference, not a time window
Two agents dispatching nine seconds apart deadlocked. The guard correctly
refused to guess between two identical waiting gates, then told the operator to
"approve yours in the UI". An agent has no UI, and nothing in the REST API binds
a run to a Testbox ID, so the recovery was unusable by the caller the lane is
built for. One trial burned 11 minutes and an extra box getting out of it.

Snapshot the waiting gates before dispatching and take the set difference after.
That is exact where a 120 second window is not, since another operator lands
inside any window you pick. On the rare true tie the instruction is now
deterministic and needs no browser: stop your box, re-snapshot, dispatch again.

Also record the trap the same trial nearly fell into. Correlating a box to a run
by timestamp looks right and is wrong: Blacksmith rewrites a box's CREATED value
as it hydrates, measured moving 05:54:43 to 05:55:03 to 05:58:48 on one box. The
authoritative binding is the RUN URL column, which appears only once the box is
ready.

SKILL.md, benchmark.md, and the demo script now share the one algorithm.
2026-08-17 23:08:35 -07:00
Lawrence Chen 2765225598 docs: cover tests and lints, and move the threat model out of the way
Every trial exercised the evidence path, so the skill only ever showed
cargo build. An agent's real task is usually cargo test or clippy, which use the
same warm target/ and need no stage helper; show them.

Move the trust-boundary reasoning to references/trust-boundary.md and keep the
operative summary inline. An agent about to build needs the rule and the
enforcement, not the full argument. SKILL.md is 11.7 KB, still above the 10 KB
guideline; the remaining bulk is the approval block, and the eighth trial proved
that moving an executable step away from the file that needs it strands a live
box, so it stays.
2026-08-17 22:42:28 -07:00
Lawrence Chen b6a6403506 docs: stop the exit trap crying wolf on a clean run
Ninth trial ran all eight documented blocks literally, in order, each exiting 0,
repairing nothing. Two cosmetic inaccuracies left, both mine.

The EXIT trap printed "Testbox <id> is still running; no stop was authorized"
at the end of a run whose ceremony had already stopped the box, because it
inferred liveness from CONFIRM_TESTBOX_STOP_SHA being unset rather than from
the inventory it had just captured. On a clean run that reads as a failure. It
checks list-at-exit.log now.

And I cited the changed-file overhead gap as "several times larger, 8.1s against
1.2s". It measured 1.48s this run with no change to the lane. The guidance not
to read that gap as sync overhead stands; the magnitude does not, so the text no
longer promises one.
2026-08-17 22:38:59 -07:00
Lawrence Chen 93cba89d64 fix: the poll guard I added never ran, and stranded a box
Eighth trial ran the documented blocks literally, as instructed, and the
warmup block stranded a live 32 vCPU box. `grep -c .` exits 1 when the count is
zero, and the plan sets `set -euo pipefail`, so the assignment
`waiting_count="$(... | grep -c .)"` ended the script on the first poll. Zero
waiting runs is the normal state right after warmup returns, so the 150s poll
loop was unreachable dead code, and the exact failure commit 8d3edf5b28 set out
to fix happened again, one round later, in the fix itself. There is no error
text; the script simply stops, leaving a warmed box, an un-approved run, and a
receipt the trap correctly refuses to act on.

Guarded every count with `|| true` in both files.

tests/test_testbox_doc_blocks.sh now treats the plan as executable
documentation: it parses all 21 fenced bash blocks under the shell the plan
mandates, then executes each counting construct against an empty result to prove
it survives its normal first state. Verified red against the unguarded line and
green after. Syntax checking could never have caught this, which is why two
rounds of review missed it.

Two more from the same trial. benchmark.md never cancelled the warmup run, so
following it alone leaves the keepalive holding a runner until the 120 minute
timeout; it now cancels and polls to terminal state. And the setup artifact is
required evidence whose command sat in prose rather than a fenced block, so a
literal run never captured it.
2026-08-17 22:21:26 -07:00
Lawrence Chen 9ebfec987c fix: put the approval in the code block that needs it
Seventh trial ran smooth end to end, zero unexpected failures, and still found
that benchmark.md's warmup block goes straight from the receipt to
`status --wait`. The approval existed only in the surrounding prose, so anyone
copying the block parks at the environment gate, burns the full 15 minute
timeout, and strands a warmed 32 vCPU box. Prose next to a code block does not
protect someone running the code block. The approval, with the polling guard and
its own attempt transcript, is now in the code, along with the DISPATCH_EPOCH
capture it binds to. Removed the unreachable second warmup_status check while
there.

Also qualified the overhead column. The changed-file gap measured 8.1s against
1.2s for the other stages while all three synced strategy=skip, because that
stage's backup, edit, restore, and re-verify work runs inside the CLI call but
outside wall_seconds. It is real, it is not sync and transport, and
operations.md now says to compare overhead only across first-clean and
incremental-noop.

Every bash block in the plan is now syntax-checked as a unit.
2026-08-17 21:58:45 -07:00
Lawrence Chen 8d3edf5b28 fix: stop the approval guard from aborting a warmup that was fine
Sixth trial reported the run as not smooth, and it was right. The guard I wrote
in SKILL.md Step 3 queries for waiting runs the instant warmup returns. GitHub
had not surfaced the run yet, so zero matched, and the guard printed "more than
one run waiting; approve yours in the UI" and exited. Zero runs is not
ambiguity, it is "not yet". Under benchmark.md's set -e that abort left a live,
un-approved box on the org inventory with its receipt already written, and the
plan's own trap is designed to refuse to stop it.

The guard now polls for 150s, distinguishes zero from two-or-more, and says
which happened. It also tells you that an abort means you already own a running
box, so stop it before retrying or the next dispatch leaves two boxes and one
receipt.

The trap also lied about why it gave up: it printed "warmup returned no owned
Testbox receipt" when the receipt existed and the real cause was
CONFIRM_TESTBOX_STOP_SHA being unset, which is the normal state. It now reports
the condition that actually held, and names the box you must stop yourself. Its
inventory file was called list-after-warmup-failure.log even on the success
path; it is list-at-exit.log now.

Two smaller ones. Record every approval attempt separately, because a retry that
overwrites the first hides a live unapproved box from the evidence pack. And
warm-before-reading has an exception for evidence runs, whose preflight and
receipt must exist before warmup; three agents in a row hit that ordering.
Finally, changed-file rebuilds cmux-remote as well as cmux-tui, so its ~8s is a
small-edit figure, not a single-crate floor.
2026-08-17 21:37:36 -07:00
Lawrence Chen f41a1ee183 Verify R2 nightly bytes before redirect (#10317) 2026-08-17 21:32:22 -07:00