One-click connect failed with "outlook has no managed OAuth path" — the
connector→provider map never got an outlook entry, though the descriptor,
broker provider, and scopes were all ready. Guard test now asserts every
managed connector has a mapping.
- Boot: sidecar ships as a onedir bundle via the resources slot (onefile
self-extraction cost ~7s of splash every launch; now ~0.5s warm); build
scripts stage+sign the folder; splash wordmark aligned to the traffic
lights; minimumSystemVersion 12.0 (whisper.cpp build floor).
- Cloud sign-in: connection restore runs in the background after the
callback responds; GUI polls fast (500ms) while a sign-in is in flight.
- Onboarding: provider page keeps unsaved drafts across switches and shows
the connected state on the form; tools page rebuilt to the owner design
(value headline, Secure-by-design card, single primary action).
- Composer: OS file drags reach the existing drop handlers in the desktop
shell (native drag-drop interception disabled + window drop guards).
The broker-bounce change (eda23c9, 07-09) pointed the authorize leg's
redirect_uri at the broker's stable callback but left the token
exchange sending the old loopback URI. RFC 6749 requires the exchange
redirect_uri to byte-match the authorize one, so the auth server
rejected every exchange — all cloud sign-ins since 07-09 failed with
'token exchange failed' after a successful browser leg (caught by the
owner's fresh-install walkthrough; also explains the earlier onboarding
Slack-connect failure, which begins with sign-in).
The unit test now derives the redirect_uri from begin_login's authorize
URL and pins the exchange to it — the old test mocked the POST without
asserting redirect_uri, which is exactly how this slipped.
708 pytest green.
begin_login used config.port (8765) for the loopback redirect, but the
packaged shell runs the sidecar on a random free port — Auth0 finished
and the browser hit a port nobody was listening on ("Firefox can't
connect"), the same bug the connector flow fixed via COWORKER_PORT.
Loopback can't simply follow the real port because Auth0 rejects
unregistered ports, so the redirect now targets the broker's stable
/v1/auth/callback and the actual port rides as state's `.port` suffix;
the broker bounces the browser home and GET /auth/callback completes
PKCE unchanged.
The desktop half of the GitHub App relay (github-relay-spec §13 Step 3):
- relay_client.py: extract RelayHub — ONE authenticated cloud socket owns
the transport/read-loop/reconnect watchdog; frames fan out by provider
tag. SlackRelayAdapter becomes its first consumer (public surface
unchanged); GitHubRelayAdapter its second. Also pin the relay tests'
name resolution to a dead loopback port — unstubbed lookups were
reaching slack.com and intermittently blowing the 2s dispatch window.
- github_relay.py: GitHubRelayAdapter — github:owner/repo#N addressing,
installation_id rides source.team_id so the gateway's per-team
allow-list (park → allow & deliver) applies per installation unchanged;
missed/revoked frames handled; send() posts an issue/PR comment with a
minted installation token.
- github_installs.py: github:install:<id> profiles (metadata ONLY — no
token field by design), default pointer + per-installation allow-lists
on the profile; last-removal turns relay off without resurrecting a
stored manual PAT.
- cloud.py: github_installation_token — memory-only ~50-min cache, force
re-mint for the 401 path, never written to the secret store;
per-installation cloud disconnect; managed-connect flow passthrough.
- Managed OAuth callback branch (token-less by design, so it precedes the
access_token check) + gateway hot-add; per-installation disconnect and
status routes; allow-list edits target github:install:<id>.
- Tools: _github_call resolves auth per repo owner — manual PAT wins
untouched, managed profiles mint and re-mint once on 401; new
github_reply + github_review (approval-gated). The §12.5 write ceiling
is enforced by what exists: no push/delete/settings tools on any path.
- github descriptor: two_way + managed.
655 pytest + 23 vitest green; 13 new tests cover install callback,
per-installation allow-lists, hub fan-out, missed/revoked, addressing,
send, token cache/re-mint, tool auth on both paths.
Accounts: each sign-in lands in its own gmail:account:<email> profile;
gmail:default shrinks to the default pointer + filters. A legacy token-bearing
default migrates lazily into one account (no user action). The three gmail
tools take an optional account argument (default fallback), name the mailbox
in results and approval args, and broker refresh targets account-keyed
profiles.
Filters: senders (addr or @domain) and label names, enforced in the DESKTOP
tool layer - matching messages silently omitted from search, a direct fetch
reads like a real 404, no tombstone an agent could probe. The hidden count
rides a _display sidecar on the tool-result message (mirroring source):
persisted for the GUI, stripped from every provider feed, audited as
rule+count, and shown on the tool card / step-group line.
GUI: GmailDetail page (Accounts with Default badge + per-account disconnect,
one-click add, filter chip rows, collapsed Tools); routes for account
disconnect/default and PATCH filters.
The desktop shell runs the sidecar on a random free port (to coexist
with a hand-run server on 8765), but begin_managed_connect built the
loopback redirect from config.port (default 8765). In the packaged app
the OAuth callback therefore hit port 8765 — a different process (or
nothing) — instead of this sidecar, so managed connect failed for every
connector. run.py now publishes the real bound port as COWORKER_PORT and
the redirect follows it. Regression test added.
Phase 3 of the cloud platform spec. The sidecar gains the two loopback
routes: GET /auth/callback completes Auth0 PKCE sign-in (started via
POST /v1/cloud/login, which opens the system browser), and
POST /oauth/callback receives the broker's form-POSTed token payload and
writes a connector profile through the managed setup path. Profiles are
field-compatible with manual paste; managed extras (refresh_token,
connection_id) enable broker refresh — hooked into the tools' _profile
choke point so managed tokens renew just before expiry — and best-effort
cloud metadata disconnect.
Sign-in is strictly optional: manual token paste works signed out and
stays available after sign-in (one-click is an addition, never a
replacement). Managed capability ships for gmail + google_calendar;
other providers join with their Phase 4 waves.
GUI: cloud account card on Integrations ▸ Connectors, one-click connect
button on managed connectors when signed in, manual fields always
rendered. Cloud endpoints are config values (cloud_base_url etc.), never
constants. Tests: 18 python + 2 e2e.