Skip to content

fix(installer): download Compose files from the pinned release - #3932

Merged
elie222 merged 1 commit into
elie222:mainfrom
tc3oliver:fix/install-images-release-tag
Nov 7, 2026
Merged

elie222 merged 1 commit into
elie222:mainfrom
tc3oliver:fix/install-images-release-tag

Conversation

@tc3oliver

@tc3oliver tc3oliver commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Why

.env.images.example says to pin a release tag for a fixed version, and the docs say to set the tag in .env and rerun install-images.sh. The rerun still downloads docker-compose.images.yml from main, so a pinned image starts under a newer Compose file.

What changed

  • When RAKAZO_IMAGE_TAG is a release (vX.Y.Z), the installer downloads its files from that tag. The shell value wins over .env, as it does in Compose.
  • edge, latest, other tags and an unset tag still use main.
  • RAKAZO_DOWNLOAD_BASE, --local and --offline behave as before.
  • A custom image with a vX.Y.Z tag that doesn't exist in this repo now fails with the download error. RAKAZO_DOWNLOAD_BASE, or --local with the files already in place, gets past it.
  • Added a sentence on this to the English and Chinese self-host guides.

How tested

  • Added cases to install-images-pull-never.smoke.sh: no pin, edge, a non-release tag, a pin from the shell, a pin from .env (with export, quotes, a comment and CRLF), the shell over .env, and a mirror base. The shell pin case failed before the fix, which stopped the script there.
  • Ran that smoke with macOS bash 3.2; CI runs it on Linux.
  • Ran the real installer with only docker stubbed: the first run wrote main's Compose file, and after setting v0.8.0 in .env the rerun wrote a byte-identical copy of the file at that tag.

Follow-up: #3933 reads the tag the way Compose does when = has spaces around it or the shell value is empty.

The docs say to set RAKAZO_IMAGE_TAG in .env and rerun the installer, but the
rerun still downloaded the Compose file from main, so a pinned image started
under a newer file. A vX.Y.Z tag now downloads from that release.
@vercel

vercel Bot commented Nov 7, 2026

Copy link
Copy Markdown

@tc3oliver is attempting to deploy a commit to the Inbox Zero Team on Vercel.

A member of the Team first needs to authorize it.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Download Compose files from the pinned image release

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 10-20 Minutes

Grey Divider

AI Description

• Download Compose files from a pinned release instead of main, keeping them aligned with the
 image tag.
• Preserve main for non-release tags and honor custom download bases.
• Test tag precedence and document release downloads in both self-host guides.
Diagram

graph TD
  Tag["Shell or .env"] --> Resolve["Shell precedence"] --> Override{"Custom base?"} -->|no| Release{"Release tag?"} -->|yes| Pinned["Release URL"] --> Downloads["Compose files"]
  Override -->|yes| Mirror["Mirror URL"] --> Downloads
  Release -->|no| Main["Main URL"] --> Downloads
Loading
High-Level Assessment

Selecting the repository ref before constructing the existing download base is the smallest change that keeps release files aligned with pinned images. Delegating tag resolution to Docker Compose was considered, but would add an early dependency on Compose configuration before its file is downloaded.

Files changed (4) +53 / -1

Bug fix (1) +10 / -1
install-images.shSelect the Compose download ref from the image tag +10/-1

Select the Compose download ref from the image tag

• Reads the image tag from the shell or '.env', preferring the shell, and uses matching 'vX.Y.Z' releases as the default download ref. Other tags continue to use 'main', while 'RAKAZO_DOWNLOAD_BASE' remains authoritative.

infra/compose/install-images.sh

Tests (1) +39 / -0
install-images-pull-never.smoke.shCover download refs and tag precedence +39/-0

Cover download refs and tag precedence

• Adds smoke cases for unpinned, release, edge, and non-release tags; '.env' parsing; shell-over-'.env' precedence; and custom download-base precedence.

infra/compose/install-images-pull-never.smoke.sh

Documentation (2) +4 / -0
self-host.mdExplain release-pinned Compose downloads +2/-0

Explain release-pinned Compose downloads

• Clarifies that a release-form 'RAKAZO_IMAGE_TAG' makes the installer download Compose files from that release rather than 'main'.

docs/self-host.md

self-host.mdDocument release downloads in the Chinese self-host guide +2/-0

Document release downloads in the Chinese self-host guide

• Adds the same release-pinned Compose download guidance for Chinese-language readers.

docs/zh-CN/self-host.md

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. An empty shell tag selects the wrong files 🐞 Bug ≡ Correctness
Description
install-images.sh falls back to .env whenever ${RAKAZO_IMAGE_TAG:-} is empty, even if the
variable is explicitly exported by the shell. With RAKAZO_IMAGE_TAG= in the shell and a release
tag in .env, it downloads that release’s Compose files while Compose uses the shell’s empty value
and resolves the images to edge.
Code

infra/compose/install-images.sh[R7-8]

+if [[ -z "$image_tag" && -f .env ]]; then
+  image_tag=$(sed -nE "s/^[[:space:]]*(export[[:space:]]+)?RAKAZO_IMAGE_TAG=[\"']?([^\"'#[:space:]]*).*/\2/p" .env | tail -n 1)
Evidence
The new fallback reads .env for an empty shell value, and the release regex then selects the file
ref. The installer passes .env to Compose, whose image references use ${RAKAZO_IMAGE_TAG:-edge};
an exported empty shell value therefore produces edge rather than the release tag.

infra/compose/install-images.sh[5-14]
infra/compose/install-images.sh[413-435]
infra/compose/docker-compose.images.yml[334-339]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An exported empty `RAKAZO_IMAGE_TAG` must take precedence over a release tag in `.env`, as it does for Compose interpolation.
## Fix Focus Areas
- infra/compose/install-images.sh[5-13]
- infra/compose/install-images-pull-never.smoke.sh[295-305]
## Recommended Fix
Test whether the shell variable is set, rather than whether its value is nonempty, before reading `.env`. Add a case with an exported empty value and a release pin in `.env`; the download should use `main`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Valid tag assignments fetch wrong files 🐞 Bug ≡ Correctness
Description
The new sed expression recognizes only assignments with RAKAZO_IMAGE_TAG= and reads their values
literally, rather than resolving all .env forms Compose accepts. If an operator writes
RAKAZO_IMAGE_TAG = v1.2.0, Compose selects that release’s images but the installer downloads from
main; a later assignment in that form can also leave the installer using an earlier release.
Code

infra/compose/install-images.sh[8]

+  image_tag=$(sed -nE "s/^[[:space:]]*(export[[:space:]]+)?RAKAZO_IMAGE_TAG=[\"']?([^\"'#[:space:]]*).*/\2/p" .env | tail -n 1)
Evidence
The regex requires RAKAZO_IMAGE_TAG= without intervening whitespace, whereas the repository’s
Compose-assignment tests explicitly accept RAKAZO_IMAGE_TAG = v1.2.0 and `export RAKAZO_IMAGE_TAG
= 'v1.2.0'. The installer subsequently invokes Compose with that .env` file, and the image
reference interpolates its tag.

infra/compose/install-images.sh[6-14]
packages/core/src/compose-update.test.ts[549-564]
infra/compose/install-images.sh[413-435]
infra/compose/docker-compose.images.yml[334-339]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The installer’s regex misses Compose-supported `.env` assignments, so its download ref can disagree with the image tag Compose resolves.
## Fix Focus Areas
- infra/compose/install-images.sh[6-14]
- infra/compose/install-images-pull-never.smoke.sh[295-305]
## Recommended Fix
Resolve the effective tag using Compose-compatible `.env` parsing, including whitespace around `=`, later assignments and variable interpolation, before selecting the download ref. Add a smoke case where a release assignment uses whitespace around `=`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can hide the parts of a finding you never read, like the evidence or the agent prompt

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Playwright screenshots

No new feature frames; gallery is suite-vs-main drift.

Open screenshot gallery · Dashboard · CI run

Updated for commit e6b1962.

@elie222
elie222 merged commit 171a20e into elie222:main Nov 7, 2026
46 of 47 checks passed
amansethia98 added a commit to amansethia98/ecomnomics that referenced this pull request Nov 11, 2026
* Split ask, takeover, steering, and streaming out of db's events.ts (#3870)

events.ts held every thread write path plus the realtime stream. Move
questions and answers to thread-ask.ts, computer takeover and release to
thread-takeover.ts, steering to thread-steering.ts, and notify, eventsAfter,
follow, and ChangeLatch to event-stream.ts. createThreadEvents, append,
finalize, send, and clear stay in events.ts. Code moves verbatim; the package
index exports the same names as before.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* refactor(mobile): split the model connection screen into a sign-in hook and form components (#3872)

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Split snapshot, reactions, stop, and read state out of thread-target.ts (#3876)

thread-target.ts held sending, snapshots, reactions, stopping, and read
state together. Move the snapshot builder to thread-snapshot.ts, reactions to
thread-reactions.ts, stopping to thread-stop.ts, and read and unread marks to
thread-read-state.ts. sendThreadMessage and target resolution stay. The
snapshot now reads the message page size from routes/shared.ts, which
already defined the same constant.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* refactor(mobile): split the integrations screen into detail, add-custom, and plugin components (#3878)

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Move page-kit words into one file per language (#3879)

Each language's kit words move from labels.ts into kit/locales, and the
kit's language list is now typed against the app's locale schema with a
type-only import, so a new app language without page words fails the
typecheck. Matching a tag to a language keeps page-kit's own rules,
which differ from the app's (zh-Hans-TW, underscore tags), and its own
right-to-left list.

No behavior change.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Share OAuth refresh-failure handling and MCP tool mapping (#3880)

- tryRetireModelCredential and oauthAccountChange now hold the
  retire-on-terminal-failure and account-changed logic that pi-oauth and
  pi-credentials each copied. Each caller still decides which error it
  throws, and the errors are unchanged.
- remote-mcp builds its MCP client in one place, and remote-mcp and
  mcp-connector share the tool fields they mapped identically (input
  schema, read-only hint, batch). Name, description fallback and app
  routing stay per caller.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Split run claim, model start and attempt cleanup out of continueRun (#3881)

continueRun now calls named helpers for its self-contained stages:
claimRun (lease and mark running), runModelBackups and startRunModel
(the selected model or the first backup that starts), retryRunSetup
(requeue or fail after a setup error) and finishRunAttempt (release the
computer, start the next waiting run, close the attempt). Each body is
moved as it was; context loading and prompt assembly stay in place.

The model start helpers live in run-model-start.ts rather than
run-model.ts so tests that mock run-model's key resolvers still mock the
calls these helpers make.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Split page-kit dataviz into modules by chart family (#3882)

dataviz.tsx becomes a barrel over dataviz/shared (scales, axes, tips,
legends), columns, scatter, heatmap, diagrams, and status (source stamp
and empty chart). The barrel exports exactly the names it did before,
so the kit's component and helper lists are unchanged.

No behavior change.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* refactor(chat-ui): split the native markdown renderer into modules (#3885)

Co-authored-by: Claude Opus 5.5 <[email protected]>

* refactor(supervisor): split the supervisor into route, identity, screen, network, and exec modules (#3890)

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Move team chat ambient judging and reply delivery out of the bridge (#3891)

TeamChatBridge did everything for a team chat in one class. Move ambient
judging (evaluating observed messages, promoting a trigger, rollups) into
TeamChatAmbient and completion and failure delivery (reserve, send once,
mark delivered) into TeamChatDelivery. The bridge builds both and calls
them where it called its own methods before. The pure prompt builders move
to core's messaging-prompts.ts with their tests.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Share sandbox adapter helpers copied across providers (#3893)

- BaseSnapshotCache holds the base-snapshot lookup, background build and
  retry timing that boat and boxd each copied, with the same constants and
  computerSnapshotName hash. Provider statuses map to ready/saving/absent;
  boat keeps warning "missing" once per absence and rebuilding a removed
  snapshot, boxd keeps warning before every build.
- PortableFileBatcher replaces the hand-rolled batch/batchBytes tracking in
  boat, boxd, daytona, railway and remote-desktop; each caller keeps its own
  file count, empty-batch guard and abort checks.
- abortedCommandEvents/timedOutCommandEvents replace the identical 130/124
  event pairs.
- sandboxNotFoundError sits next to isSandboxGoneError and builds the
  provider "gone" errors.
- CreateOS uses core's cancelResponseBody instead of its copy.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* refactor(desktop): split window, setup window, and menu handling out of the main process entry (#3895)

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Load the mobile skills screen through rpcQuery (#3875)

Replace the screen's hand-written mounted guard, result state and focus
reload with the shared React Query reads, refetched on focus. Loading,
partial and Retry states stay the same; a test covers them.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Load the mobile memory screen through rpcQuery (#3877)

Replace the screen's mounted guard, newest-load counter, error state and
focus reload with the shared React Query read, refetched on focus. A refetch
replaces one still running, which keeps a slow earlier read from bringing
back a deleted note.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Load the mobile approval rules screen through rpcQuery (#3886)

Replace the screen's per-focus guard, save counter, rule, mode and load
error state with shared React Query reads refetched on focus. Rule edits
update the cached list, and a save cancels a read still running so it never
shows what the save replaced.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Load the mobile Space settings overrides through rpcQuery (#3888)

Replace the screen's per-focus guard and its own overrides and load error
state with a shared React Query read sent to the Space, refetched on focus.
Saved overrides update the cached read.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Answer what the bot is waiting on during a mobile call (#3726)

* Answer what the bot is waiting on during a mobile call

Move the decision of what a spoken call turn does into core and use it
from web and mobile. On mobile a spoken reply now answers the bot's
open question, steers a working run, points approvals to the screen,
and keeps the microphone shut while the bot waits on a typed code,
matching web.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Keep the call mic shut until the thread loads, and expect an answered run early

The call could not tell whether the bot waits on a typed code before the first
snapshot, so it now waits for it. An answer's reply can arrive before the
answer request returns, so the call expects that run from the start.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Ignore thread snapshots from an ended call's watcher

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Check every waiting run for a code, and expect early follow-up replies

A code asked by a waiting run behind a working one now holds the call's mic.
A follow-up's reply can arrive before the request returns, so any reply in
the call counts from the start. A call whose thread can never load hangs up
instead of waiting silently with its mic shut.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Hang up properly on a failed load, wait for a background reply, steer any working run

A thread that can never load now ends the call from the caller's side, so the
server hears about it. A reply from a resumed background run waits for that
run to settle before it is spoken. A working run behind a finished one shown
first still gets the spoken follow-up.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* End a call whose thread keeps failing to load

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Answer only the shown run by voice, and say what to do while a code is pending

Another waiting run can be someone else's in a shared thread, so spoken
answers go only to the run the thread shows first; codes on any waiting run
still hold the mic. When the mic is held for a code, the caption tells the
caller to hang up and enter it on screen.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Tidy the call's expected-run comment and name

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Route spoken turns by the run the thread shows first only

Other active runs can belong to other members of a shared space: holding the
caller's mic for their code, or answering and steering their runs, does not
work. Spoken turns go back to web's behavior of acting on the shown run.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Keep a newer turn's expected run, and prompt for a code found at load

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Keep older call requests from resetting a newer turn

An approval turn now counts as a turn, and an older request's failure no
longer moves a newer turn back to listening.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Hold the call mic for a code any waiting run asks for

Spoken answers still go only to the run the thread shows first, but a code
request on any waiting run now keeps the call from hearing or transcribing it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Split MCP, model, billing, and computer schemas out of contracts' domain.ts (#3861)

domain.ts had grown to hold every domain's schemas. Move MCP server schemas
into mcp.ts, model connection schemas into model-connections.ts, billing
terms and status into billing.ts, and computer schemas into computer.ts. The
space availability schema moves to its own module so mcp.ts does not import
domain.ts, which would form a cycle through events.ts. The package index
re-exports everything, so consumers are unchanged.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Screenshot web UI changes on pull requests automatically (#3901)

* Screenshot web UI changes on pull requests automatically

After a pull request's preview deploys, an agent with only browser tools
opens the preview, finds the screen the diff changes and screenshots it.
A separate trusted job checks the files and captions, uploads the images
and writes them into a marked section of the pull request description.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Keep author text when a screenshot marker is orphaned; skip closed pull requests

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Let the screenshot agent run for fork pull requests

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Address screenshot review: one preview URL, byte size limit, no links in captions

Also include web entry and public files, call Node by path under sudo,
require the artifact when the agent returned files, and require an https
public base URL.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Share run helpers copied across the executor (#3871)

- enqueueSteeringContinuation replaces seven identical "queue the steered
  run, log a failed enqueue" blocks; the takeover enqueue keeps its own
  label and stays as is.
- messageBlocksText joins a message's text blocks; callers that trimmed
  still trim, the notification body still does not.
- SCHEDULE_TIMING_FIELDS and scheduleTimingArgs are the one list of shared
  timing fields; schedule_create keeps adding watch explicitly.
- fleet-tools uses asRecord, and pi-runtime-tools uses clipToolResultText
  and TOOL_RESULT_TEXT_LIMIT instead of re-writing them.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Move messaging startup and RPC error handling out of the API's app.ts (#3884)

buildApp carried the whole messaging and team chat startup inline, with its
shutdown steps spread through stop(). Move it into messaging-runtime.ts,
which starts the same way at the same point and returns a stop() that runs
the same shutdown steps in the same order. Move handleRpcError and
logUnexpectedRpcError into rpc-errors.ts.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Move the MCP server form into its own component (#3860)

* Move the MCP server form into its own component with one draft

The MCP servers overlay kept seventeen separate pieces of form state and
rendered the add/edit form inline. The form now lives in McpServerForm,
and its values are one draft object built by pure helpers (empty draft,
draft from a stored server, saved config). The overlay still owns the
draft so a reset keeps the chosen transport exactly as before.

No behavior or visual change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Update translation catalog references

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Tell Team Computer bots where shared/ is from a shell command (#3918)

* Tell Team Computer bots where shared/ is from a shell command

File tools resolve shared/ from the Team root, but shell commands start in the
bot's own folder, so a script that saved to shared/x wrote bots/<id>/shared/x
and nobody, including the bot, could find it afterwards.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Say the shell's shared path holds from the bot home, and that cwd shared always works

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Split model secret parsing and credential refresh out of pi-oauth (#3864)

parseModelSecret, serializeModelSecret, secretValuesToRedact and the
StoredModelSecret type now live in model-secret.ts, and the
database-coordinated refresh of stored credentials (locks, retirement to a
reconnect marker, the detached refresh kick) lives in
model-credential-refresh.ts. Code is moved verbatim; importers and the
per-process test imports point at the new modules.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Split bot settings into one component per settings group (#3887)

* Split bot settings into one component per settings group

BotSettings keeps its state and save queue, and its Advanced tab now
renders notifications, model, computer, voice, permissions, replies,
description, tools, and export/clear from presentational components in
bot-settings-sections. ComputerModePicker moves there too, and the
agent import imports it from its new home.

No behavior or visual change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh translation catalog references

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Split onboarding into model and first-bot step components (#3874)

* Split onboarding into model and first-bot step components

The model step's form, sign-in, and provider choices move into one
useOnboardingModel hook that the page still holds for its whole life, so
the error shared with the bot step and signing out is unchanged. The
model step renders from ModelStep with compatible-server and
subscription sign-in subviews, and first-bot creation plus its retry
view live in their own module.

No behavior or visual change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh translation catalog references

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Split onboarding, capabilities, computer control, and connection flows out of route files (#3894)

* Split onboarding, capabilities, computer control, and connection flows out of route files

routes/mcp.ts also served the plugin marketplace, capabilities, onboarding,
and integration setup; move those into routes/capabilities.ts and
routes/onboarding.ts. Move routes/computer.ts's non-route control helpers to
computer-control.ts so the threads routes no longer import the computer
routes. Move connections' begin, complete, and revoke handler bodies and the
provider lock into connection-flows.ts. Procedure names, paths, and handler
code are unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Describe connection flows without naming providers

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Split the mobile API client into focused modules (#3898)

* refactor(mobile): split the API client into server, auth, live, and type modules

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Type the new test mocks with top-level type imports

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Split the agent import into a source-reading hook and a plan view (#3889)

* Split the agent import into a source-reading hook and a plan view

useAgentImportSource reads a folder, backup, or agent document, keeps
the newest read winning, previews it, and holds the choices a preview
starts with. AgentImportPlanReview renders what would be imported and
the new bot's name. AgentImportForm keeps the source picker and create.

No behavior or visual change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Count the split agent-import files in the import translation test

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh translation catalog references

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Build the run runtime once for the API and the worker (#3883)

* Build the run runtime once for the API and the worker

The API and the worker each wired the MCP connector, integration
settings, connector stack, run executor, job handlers and reconciler by
hand, including the copied plugin-slug lookup and redacted secrets list.
One builder in @rakazo/adapters now does it for both. The API passes its
parsed env so test overrides still apply; the worker passes a parser that
reads exactly the values and defaults it read inline before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Import the connector stack from its new module

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Let a bot hand longer work to a background helper and stay free, with Stop on the helper's card (#3101)

* Run background helpers in threads of their own, ready for bots to start

* Keep helper stops and cards apart from the chat's turn, and add helper-thread constraints without a locking scan

- Stopping a helper no longer writes run.cancelled into the chat; its card update tells the chat.
- A helper's card no longer belongs to the turn that started it, so that turn ending does not show the helper as stopped.
- A helper runs while the bot's computer is switching, since it never uses the computer.
- Helpers are not offered agent-connection tools, which message other agents.
- Clearing a chat also removes what its helpers wrote in their own threads.
- The threads constraints are added NOT VALID and validated in their own migrations.
- Drop the unused scripted "in the background" path and use a top-level type import in the db mock.

* Let a bot start background helpers and stop one from its card, and refuse built-ins a helper isn't offered

- run_subagent takes background: true. The executor starts the helper as its own run, the bot is told when to use it, and the scripted runtime starts one when asked to work in the background.
- A running helper's saved card has Stop on web and mobile, which stops that helper only; a stopped helper reads "Stopped" instead of "Failed".
- A background helper's run refuses any built-in outside the helper allowlist before the call is prepared or recorded, whatever the runtime emits.
- Starting a helper and returning its result take the chat's lock with the thread-lock transaction budget.
- The helper migrations now sort after main's newest.

* Keep a settled helper card in its place in the web chat

A helper card's update replaced the saved card by appending it after later messages, so Stop moved the card below the bot's reply until the chat reloaded. Only a live subagent placeholder gives way now; the saved card is updated where it is, as mobile already does.

* Bring a helper's result back to any member of a shared space, and let teaching start while one works

In a shared space anyone can talk to a bot another member made, and a turn
runs as whoever sent it. A helper started from such a turn settled its card
but never passed its result on, because the result only went back while the
bot was still made by the same person. It now goes back to anyone still in
the space while the bot is active, as the cloud-agent wake already does.

Teaching also waited for another member's helper to finish, though a helper
never uses the computer; it now skips helper runs like the other computer
checks.

* Keep history tools and question prompts away from background helpers

Background helpers work in a thread of their own, so the history tools they
were offered could never reach the chat that started them. Stop offering them
(and their guidance). The scripted runtime no longer starts a helper for a
question that only mentions background work.

* Keep helper work out of the opening space, relay helper results first in the scripted runtime, share the stopped-helper rule and export helper threads

* Say who can start a background helper in a shared space

* Keep a background helper in chat reply mode

A bot set to the messages reply mode put its background helpers in that mode too, so a helper was told to tell the person things with message_user, which it is not offered, and only its last working note could stand in for a missing result.

* Clear imported helper threads with their chat

Helpers start only from the bot's own chat, so clearing it now removes every
thread the bot's helpers wrote, including imported ones that have no runs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Fix clear-chat test doubles for the helper thread lookup

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Keep helper results flowing past a failed read, and clear helper summaries

A failed transcript read now retries that helper later instead of stopping the
reconciliation pass, and clearing a chat also drops its helper threads'
history summaries.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Tighten helper guidance, clearing, and imported cards

Helper runs are told only about the tools they have, Clear chat filters helper
threads in the database instead of loading every id, and an imported helper
card that was still working shows as stopped.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Keep the teacher's own helper working when teaching starts

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Offer background helpers only where a turn can start one

The background part of the run_subagent guidance now follows the same check
that starting a helper uses, so group, messaging and helper-result turns are
not told to do something that is always refused.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Eliezer Steinbock <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>

* Split the integrations overlay into views and shared connection state (#3897)

* Split the integrations overlay into views and shared connection state

usePluginConnections holds what the overlay lists and the connect,
rename, revoke, and uninstall actions every view shares.
useCatalogBrowse holds search, category, and paging. The connector
page, manage view, plugin browse, GitHub import, custom source form,
domain search, and + menu render from their own components. State that
survives switching views (the GitHub URL, domain results, the source
error) stays with the overlay as before.

No behavior or visual change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Drop the unused skills variant from the installed plugin row

The row is only ever rendered for connectors; its skills branch came over
unused from the old overlay. The Polish plural floor drops by the one
message this removes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Split the composer's file drop, triggers, and toolbar out of its body (#3892)

* Split the composer's file drop, triggers, and toolbar out of its body

useComposerFileDrop holds the window-wide file-drag tracking and the
composer's drop handlers. useComposerTriggers holds the @ mention list,
the leading-/ list, and the picked skill. Attachment chips, the two
pickers, and the send/stop/talk actions render from composer-parts.
The draft, dictation, send, and reply logic stay in the composer.

No behavior or visual change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh translation catalog references

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Move the shell's palette, space, and teaching callbacks into hooks (#3896)

* Move the shell's palette, space, and teaching callbacks into hooks

usePaletteActions builds the command menu's actions (including the
settings sections it reads), useSpaceActions renames, removes, and
leaves spaces, and useTeaching stops a skill being taught and reads its
state back. Shell calls each where it built them before.

No behavior or visual change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh translation catalog references

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Stop pnpm reinstalling dependencies before exec and run (#3917)

pnpm 11+ defaults verifyDepsBeforeRun to install, so a nested pnpm exec in a
test could relink node_modules while the suite was running. A unit test shard
on main lost vitest that way after the pnpm 12 upgrade.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Hand a finished background command's output to the next model request (#3921)

Co-authored-by: rinseaid <[email protected]>

* Open workspace files in the screen's browser on provider desktops (#3922)

On shell-driven provider desktops, open_path for workspace files no longer uses xdg-open (which hung on ImageMagick display). Files open as file: URLs in the screen browser, matching Docker computers since #1542.

* Commit attach_file's message with its artifact row (#3923)

Co-authored-by: rinseaid <[email protected]>

* Translate every web string that still showed in English (#3919)

* Fill every missing web translation

All 13 non-English web catalogs now translate every string. 4,397 entries
were empty and showed in English; only msgstr lines change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Say which login a Traditional Chinese replace warning replaces

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Say the Dutch automatic review asks for approval of the rest

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Name the shared space in the rule about who sees its chats

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Offer Google Chrome as a computer image browser (#3925)

* Offer Google Chrome as a computer image browser

BROWSER=chrome installs Google Chrome stable from Google's apt
repository beside the existing chromium and brave choices; the default
stays Chromium. A separate stage fetches Google's signing key, checks
it by fingerprint, and gives apt only that key. Other builds never
contact Google, and a Chrome build on a platform Google does not
publish for fails with a clear message instead of falling back.

The browser policy is installed for Chrome too, and the focus helper
looks for the window class of the browser the image actually holds, so
an image with only Google Chrome raises its window instead of opening
a second browser.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs: describe the browser profile's effect on risk checks as possible, not certain

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Add a translate skill so agents can fill missing translations (#3920)

* Add a translate skill that fills empty web catalog entries

catalog-gaps.ts lists untranslated entries, fills only empty ones from a
JSON map, and checks every translation keeps its placeholders and tags.
The skill tells any agent how to translate, verify and ship them, on the
catalog sync pull request when one is open.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Read multi-line catalog entries whole and catch a dropped plural number

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Bring a translation branch up to date with main before translating

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Keep a translation branch's translations when merging main

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Stop only when the catalog check passes, and push sync-branch updates first

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Run the full unit suite when the translate skill changes

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Write filled translations in Lingui's own format and key explicit ids apart

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Start computers in their owner's time zone and language (#3927)

* Start computers in their owner's time zone and language

Docker computers ran on UTC with the browser's default language,
whatever the account said. Provisioning now reads the computer owner's
time zone and app language and passes them to the sandbox provider as
optional fields. The Docker supervisor keeps only a valid IANA zone and
language tag, sets TZ and LANGUAGE on the computer, and replaces a
computer whose values changed on its next start, keeping its home.

Chrome reads LANGUAGE only when it creates a profile, so the browser
launcher also passes --accept-lang, which reaches profiles saved
earlier. Missing or unreadable values keep today's defaults, and other
providers ignore the new fields.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Give computers the runtime's spelling of the account time zone

Intl accepts a zone name in any case, so an account can hold
europe/kyiv, but the computer's zone files are case-sensitive and libc
fell back to UTC for it. The supervisor now always sets TZ to Intl's
resolved name, which may be a legacy link such as Europe/Kiev; the
image's tzdata ships those links. The docs also note that a custom
computer image should not set TZ or LANGUAGE itself.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Give attach_file's message write the thread-lock transaction budget (#3928)

The message that attach_file posts inside the artifact transaction waits
for the thread row, which a snapshot read can hold FOR SHARE for up to
15 s. withResolvedArtifactVersion ran that transaction on Prisma's 5 s
default, so an attach behind such a read expired with P2028, which is
not retried, and the tool removed the stored file and returned an error
where publishMessage used to wait. withResolvedArtifactVersion now takes
transaction options and attachWorkspaceFileToThread passes
THREAD_LOCK_TRANSACTION_OPTIONS, as the other thread writers do since
#3809.

Co-authored-by: rinseaid <[email protected]>

* fix(installer): download Compose files from the pinned release (#3932)

The docs say to set RAKAZO_IMAGE_TAG in .env and rerun the installer, but the
rerun still downloaded the Compose file from main, so a pinned image started
under a newer file. A vX.Y.Z tag now downloads from that release.

* fix(installer): read the tag the way Compose does (#3933)

Compose accepts spaces around = in .env and lets an empty shell value win over
.env. The installer did neither, so it could download different files from the
images Compose then started.

* Read the new page when a purchase check starts mid-navigation (#3942)

The purchase check found the tab's isolated world and then read it. When
the tab replaced its document in between, as a login redirect does,
Chromium answered "Cannot find context with specified id", the check
failed, and the bot asked for approval of a payment that did not exist.
Read the new document's world again a few times; a page that keeps
losing its document still fails and asks.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* fix(android): prevent blank notifications when bots pin each other (#3943)

* fix: support extended model timeouts and disabled idle watchdog (#3228)

* Keep the bot screen on view while control switches (#3948)

A new screen of the same bot now loads in a new frame while the frame it
replaces stays on top until the new one reports back, so taking control
no longer blanks the screen. Releasing control shows the bot's last
read-only screen at once instead of after the release call, and goes back
to control if the release fails.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* docs: add concise Thai guides (#3953)

Add README.th-TH.md and concise Thai guides under docs/th-TH/ for self-hosting, server admin, and daily use. Commands, env vars, paths, and URLs stay in English. No UI locale.

* Keep translation catalogs out of feature pull requests (#3717)

* Keep translation catalogs out of feature pull requests

Feature PRs no longer rewrite the 15 web catalogs. Production builds extract a
gitignored template first, so a string missing from the catalogs compiles to its
English text instead of a hashed id, and a workflow on main keeps one
"Update translation catalogs" PR in step with main.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Run catalog sync only on main and document the new-language exception

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Delete the catalog sync branch only if it is still the checked commit

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Publish only edits to existing catalogs from the sync workflow

The check read only a rename's old path, so a forged patch could rename a
catalog to another file under locales. Rename detection is now off, paths are
NUL-delimited, and only in-place edits to existing messages.po files pass.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Keep the catalog PR if any of its commits is not the bot's

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Read the runner temp dir at step level in the catalog sync workflow (#3956)

GitHub rejects the runner context in job-level env, so the workflow
failed before any job ran.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Mobile: drop a sign-in that finishes while a server switch is still saving (#3955)

A server switch clears the session before it stores the new server URL.
A sign-in that started and answered inside that window passed both
checks and saved the old server's token, which the app then sent to the
new server. Refuse to save while a switch is in progress and end the
session on the server that issued it, as for a sign-in a switch overtakes.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Redact MCP launch arguments under *_auth names and numeric named keys (#3954)

* Redact MCP launch arguments under *_auth names and numeric named keys

A launch argument such as --client-auth <token> was not read as a
credential, because only a bare auth name was. Values under access_key,
private_key and secret_key were dropped when numeric, unlike other
explicit credential names. Both now register for redaction, while
--no-auth true and --auth-mode basic stay readable.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Keep the argument after an auth switch such as --no-auth readable

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* fix(notifications): stop alerting on agent-to-agent turns the chat stays read for (#3957)

Bot-to-bot turns stopped marking chats unread in #1142, but the server still
pushed "finished" for each one and the Android poller posted "replied" for each,
so bots messaging each other fired a stream of alerts. A completion now pushes
only when it marks the chat unread, and the poller leaves agent-to-agent
completions to the server push.

Also undo the #3943 Android changes that rested on a wrong premise: Expo nests
push data under data["body"], so merging top-level FCM keys was not needed, and
dropping title-less pushes discarded plain pushes that Expo renders itself.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Stop reporting chunks that did not load and aborted space-switch transitions (#3958)

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Open the web app when the browser blocks local storage (#3959)

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Report push and error-tunnel failures once (#3962)

A failed push notification was reported twice: the push adapter logged an
error and then threw to a caller that logs it again. The adapter now only
throws.

The error-report tunnel answered 502 when the collector itself was
unreachable or failing, and that 502 was reported to the same collector as
a server error. Those failures are now marked expected, so the request
completes as a warning.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Log computer provider outages as warnings and classify two more provider refusals (#3963)

Computer updates, team screen releases and plot rendering reported provider
outages, rate limits and slow starts to error reporting on every occurrence,
though work waits and tries again. They now log a warning; an unpaid provider
account is still reported.

A resume refused because the provider is at capacity, and a machine still
refused as stopped after it was woken, now fail as a computer that did not
start, so runs retry and idle sleep tries again later.

Removing a bot's folders from a stored home no longer fails when the computer
is deleted during the save.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Run migrations on every deploy, not only when Compose finishes first (#3964)

The deploy checked for the migrate service with `config --services | grep -qx migrate`
under pipefail. grep -q exits at its first match, so when Compose was still writing
the service list it died of SIGPIPE, the pipeline failed, and the deploy skipped
migrations and started the new API against the old schema.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Log which fields an RPC response got wrong when output validation fails (#3967)

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Stop flagging a resumed run as stuck in the queue (#3968)

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Retry a model stream the provider reports as interrupted (#3969)

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Show why a routine's webhook key cannot be set instead of failing silently (#3960)

* Show why a routine's webhook key cannot be set instead of failing silently

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Drop a webhook key failure that arrives after leaving the bot's editor

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Keep the creator reason to the webhook key step of saving a routine

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Hide catalog apps that have no way to sign in (#3961)

* Hide catalog apps that have no way to sign in

The connector provider lists every app it has tools for, including ones
like Shopify and Twitter where it runs no shared sign-in. Without the
project's own sign-in setup for them, Add could only fail with "This app
needs its own sign-in setup to connect." The catalog now leaves those
apps out until a setup exists, keeps them for anyone already connected,
and lists everything as before if the lookup fails.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Keep each provider key's app catalog separate and log failed sign-in lookups

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Make the fake provider refuse sign-in for apps without one

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Read every page of auth configs in the fake provider

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Keep apps listed while the user still has an account for them

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Never hide an app the directory marks as needing no sign-in

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Treat the preview-link comment as status chrome in pr-digest (#3973)

* Treat the preview-link comment as status chrome in pr-digest

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Treat Qodo's review-updated notice as status chrome in pr-digest

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* End live streams when their request aborts, before re-checking the session (#3966)

* End live streams when their request aborts, before re-checking the session

A stream whose source ignored the abort kept looping until the next
heartbeat, and a source that did end ran one more session re-check. On a
deploy the old API destroys open sockets and then ends its database pool,
so those re-checks failed with "Cannot use a pool after calling end on the
pool" or a connection timeout and were reported as errors.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Re-run checks

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Race the request abort per read so delivered events aren't retained until disconnect

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Skip the first read when the request has already aborted

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Answer a repeated account deletion with a conflict and drop the auth log echo (#3970)

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Answer a model provider's sign-in outage as a retryable 503 without an error report (#3971)

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Answer 402 when starting a plan early leaves the card uncharged (#3965)

Ending a trial early charges the card at once. When the bank needs the
cardholder to confirm the payment, or declines it, the billing provider
answers 402 and the trial runs on, but the server answered 500 and
reported it as an error. The billing adapter now turns that 402 into a
provider-neutral "payment not completed" error, and the billing service
answers 402 for it.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Give each member of a shared space their own unread state and notifications (#3902)

* Give each member of a shared space their own unread state and notifications

Read state moves from the thread to a per-member thread_reads table, so one
person reading a chat no longer reads it for everyone. A bot's message marks
the chat unread for every member; a person's message for everyone else.
Finished replies notify every member with their own badge; approvals,
takeovers and failures stay with the person whose run it is. A member's
message notifies the others, never its author.

The old columns are dropped in a deferred migration; until then a trigger
mirrors unread marks the previous release writes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Format a notification test

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Divide at a teammate's message, keep a concurrent unread, skip call turns

The New divider now starts at the first message new to the viewer, a
teammate's included. Mark read locks the member's read row, so a Mark as
Unread from another tab is never overwritten. Spoken call turns no longer
notify teammates.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Share the unread rule, quiet desktop alerts for call turns, steady the E2E

The divider reuses messageUnreadFor instead of restating it. Desktop
notifications for a teammate's message skip spoken call turns and
file-only messages, as the push does. The shared-spaces E2E puts the
invitee in another chat before the owner writes, so the invitee's shell
can't open, and read, the new chat on its own.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Mirror the old release's reads, serialize unread marks with clears, batch pushes

During a deploy the trigger now also mirrors the previous release reading
or clearing a chat, and seeds a member's divider from the reply it wrote
rather than the stale shared cursor. Mark as Unread holds the thread like
mark read does, so a clear can't be undone by a concurrent mark. Pushes go
out a few members at a time.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Share the member alert rule; keep bot-to-bot replies with the run's owner

Whether a teammate's message alerts the space now lives in core, used by
both the push and the desktop alert. A finished run triggered by another
bot notifies only its owner, as before shared spaces.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Mirror the old release's Mark as Unread cursor; drop unread from the benchmark seed

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Backfill read state atomically; reactions don't alert teammates

The migration locks threads from the backfill until the mirror trigger is
in place, inside one transaction, so a write from the previous release
can't fall between them. A reaction no longer raises a teammate's desktop
alert or push.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Register OAuth clients with a method the server accepts (#3975)

* Register OAuth clients with a method the server accepts

Dynamic client registration always asserted
`token_endpoint_auth_method: "none"`. An authorization server that does
not offer that method rejects the whole registration document with
`invalid_client_metadata` rather than registering the client with a
method it does support, so the connection can never be authorized.

Choose the method from the server's own RFC 8414 metadata instead,
narrowed to the methods this client can perform, and omit the field
when the server advertises none so the server applies its own default.
A registration that returns a client secret is stored and used for the
token request as before.

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

* Ask for a pre-registered client when no method can be performed

A server that advertises client-authentication methods but none this
client can perform was falling through to an omitted field, which
registers for `client_secret_basic` under RFC 7591 -- a method the
server never offered -- leaving a client that cannot authenticate.

Treat that as a server only an out-of-band client can sign in to, which
is what the existing pre-registered client asks the user for. A server
that advertises nothing still gets an omitted field and applies its own
default. A configured client's document is read for its scope alone, so
its method, settled out of band, is left untouched.

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

---------

Co-authored-by: Bryan Taggart <[email protected]>
Co-authored-by: Claude Opus 5 <[email protected]>

* Give inbox chats in every space the iPhone long-press menu (#3977)

* Give inbox chats in every space the iPhone long-press menu

On iPhone only chats in the selected space had the row menu, so holding a
chat listed under another space opened it on release instead. Every row now
gets the menu, its Move to lists that chat's own space's sections, section
edits and Delete go to the chat's space, and a long press never opens a chat.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Drop a cross-space delete quietly when the session changes before it asks

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Archive iOS chats with a full swipe, also from the activity view (#3976)

* Archive an inbox chat with a full swipe on iOS

Swiping a row left already revealed Archive, but full swipe was disabled,
so archiving took a swipe and a tap. A full swipe now archives, like Mail.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Swipe and long-press chats in the activity view on iOS

Rows under the bell had no swipe actions or menu. They now get the same
ones as inbox rows, and leave at once when their chat is archived.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Type the activity test's empty fold set

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Fix: Add Cloudflare free-tier model to recommended IDs (#3980)

This adds a free-tier Cloudflare Workers AI model to the list of RECOMMENDED_MODEL_IDS to fix connection errors for free accounts.

* Open Rakazo Cloud on the desktop app's first launch (#3984)

* Open Rakazo Cloud on the desktop app's first launch

A first launch now goes straight to Rakazo Cloud instead of asking which
server to use. Choosing another server moves to a muted "Change server"
link under the sign-in form (and the existing app menu item). If Cloud
cannot be reached, the setup window opens with the reason.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Keep forced setup out of desktop e2e runs that open a server directly

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Share the desktop startup probe between saved servers and first-launch Cloud

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Stop reporting model failures the user fixes as server errors (#3985)

* Stop reporting model failures the user fixes as server errors

Classify the new model-provider cyber refusal wording as unusable output, a model that only
serves another endpoint as a model choice, Rakazo's own out-of-credits message and a
free-tier prompt limit that asks to upgrade as credit limits.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Keep Rakazo's out-of-credits failure off the provider-limit explanation

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Show a paid-plan model refusal as an access denial, not a credit limit (#3986)

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Show bots at work on the iOS Lock Screen and Dynamic Island (#3981)

* Show bots at work on the iOS Lock Screen and Dynamic Island

One Live Activity per phone sums up every bot of the user's at work:
the bot's name, its latest step, and "Needs you" when it waits on the
user. The app starts it when the user sends; the server updates and
ends it by push as any of the user's runs change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Protect the new card table with row-level security and fix import order

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Keep the bots-at-work card honest across restarts, sign-out and missed pushes

- Re-check the card with the server whenever the app opens, so one whose
  ending push went missing is ended then; keep it through failed requests.
- End the card on sign-out, and watch the token of a card from an earlier
  launch.
- Find whose card a thread change touches from its active and just-ended
  runs, not its newest twenty, and still report a change whose realtime
  publish failed.
- Add new widget sources on an existing project too, not only when the
  widget target is created.
- Drive the throttle test with a fake clock.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Cover the bots-at-work card registration in the authorization test

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Seal the card's last state, retry pushes that did not go through

- Store the last state sent encrypted, like the push token: its rows hold
  bot names and steps.
- Retry a push that did not go through a few times with backoff, and end a
  card only once its ending push went through.
- Keep a card on BadDeviceToken, which also means a push environment
  mismatch.
- Name a card started from a group chat after the app until the server's
  first update names the bot.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* End the bots-at-work card with the session that registered it

A card now records the session that registered it. Updates go only to
cards whose session is still signed in; a card whose session was signed
out, expired or revoked gets one ending push with what it already showed,
and a replaced session's cards move to the replacement.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Follow every thread change, keep questions on the card, and drop the per-update seal

- Observe thread changes on each process's realtime fanout, so changes
  published outside the thread events service, such as an expired
  takeover wait, also update cards.
- Keep a bot "waiting" when a question in its chat is still open beside
  newer work there.
- Store a keyed digest of the last state and its waiting count instead of
  sealing the state on every update; an ending card is sent no rows.
- Index runs by user and status for the per-user lookup.
- Start the app's card listeners from the root layout, so the card is
  re-checked on launch and whenever the app opens, from any screen.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* End the card only on a confirmed sign-out, keep end pushes for offline phones

- End the card on a session change only once it has landed and the
  keychain read shows no session; a renewal or a failed read keeps it.
- Keep an end push queued at Apple for the Live Activity's 8-hour lifetime,
  so a phone offline for a while still drops the card.
- Let a long bot name truncate before the status on a row.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Start the bots-at-work card checks only once the saved server is restored

On launch the check sends the session to the server; before the saved
server and its headers load, that could be the build's default server.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Cap live status cards per user and share Apple reply handling

- Keep at most five cards per user, dropping the least recently
  registered, so one account cannot make each update fan out widely.
- Read Apple's replies in one place for VoIP and Live Activity pushes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Find a bot's step behind long streams, fold helpers into their bot, ignore stale replies

- Page back through a run's progress, a bounded way, so a long stream of
  text after the last step does not blank the card's step line.
- Skip background helper runs, which work for a bot in hidden threads and
  would otherwise show as more bots.
- Ignore a card registration reply that lands after the account changed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* End a card the per-user cap drops instead of leaving it without updates

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* End cards on account deletion and handover, clear steps the bot clears

- End a deleted account's cards before their rows go with it.
- Start a card over when another account registers it, and write a card
  only while the user syncing it still owns it.
- Show an empty step when the bot clears its last one.
- Start a new card for a send that lands while the last one is ending.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Read only new progress for a card's step, seal tokens only for new cards

- Remember each run's latest step and the newest progress read, so later
  updates read and decrypt only newer progress, not a long text stream.
- Refresh an existing card on re-registration, as each app open does,
  without sealing its token again.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Stop blaming the model provider for Rakazo's own run failures (#3987)

A run that fails inside Rakazo is saved as "Internal server error", which the error card
labelled as a model provider error, sending people to check a provider that was fine.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Say why computer hours ran out and link to billing (#3988)

* Say why computer hours ran out and link to billing

A run blocked by the computer allowance only said "No computer hours left.",
so trial users who had picked a plan believed they had paid and could not
tell what to do. The chat error now names the cause (trial hours, a
cancelled trial, or this month's plan hours) and offers the billing action
that fixes it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Translate the computer hours reasons on mobile

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Give a paid own-key subscription its computer hours whatever funds its models (#3990)

Computer hours read the organization's model funding to pick between the
own-key tier and the credit plan's share. An own-key subscriber who bought a
one-time top-up (which switches model funding to credits), or who never chose
a funding mode, fell through to the credit formula and got 0 hours while
paying. A live own-key subscription now always gets its tier's hours and
machine size.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Offer an App Store update from Settings when a newer native version is out (#3989)

* Offer an App Store update from Settings when a newer native version is out

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Center the update button under the version

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* fix(web): clear macOS traffic lights for Close page and Open navigation (#3974)

* fix(web): clear macOS traffic lights for Close page and Open nav

Share DARWIN_WINDOW_CONTROLS_WIDTH_PX across WindowChrome, PageView, and chat-header so Close page and Open navigation sit clear of the traffic lights on darwin (#3450, #3757).

* fix(web): tests and chat-header spacing for macOS traffic lights

* fix(web): PageView inset and header tests for macOS traffic lights

* test(web): assert macOS traffic-light clearance in pages e2e

* chore: temp fetch helper for pages e2e

* test: size probe 20k

* test(web): assert macOS traffic-light clearance in pages e2e

* test(web): assert macOS traffic-light clearance in pages e2e

* test(web): restore pages e2e with macOS traffic-light clearance asserts

Previous commit accidentally replaced pages.spec.ts with a path stub. Restore the full suite and keep bbox x>=72 + feature screenshots for Close page (#3450) and Open navigation (#3757) so PR comment embeds frames.

* chore: remove tmp-fetch-pages-spec.sh tunnel helper from PR branch

* test(web): restore pages e2e with macOS traffic-light clearance asserts

Previous commit left pages.spec.ts as a path stub. Restore the full suite
and keep bbox x>=72 + feature screenshots for Close page (#3450) and Open
navigation (#3757) so the Playwright PR comment can embed feature frames.

* Put the split-view inbox header buttons on Liquid Glass (#3992)

Co-authored-by: Claude Opus 5.5 <[email protected]>

* Snooze a chat until later (#3979)

* Snooze a chat until later

Right-click a bot or group (long-press on mobile) and choose Snooze: 1 hour,
3 hours, tomorrow at 9 or next Monday at 9. The chat leaves the list until
then and comes back marked Woke until it is opened. Snoozed chats wait in a
folded Snoozed shelf pinned to the bottom of the sidebar, where they can be
unsnoozed early.

Each member snoozes for themselves: the wake time is a nullable column on
their thread_reads row, so it works for bots and groups alike, and a shared
space's other members keep the chat.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Format the snooze rules test

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Cover snooze in listings, clears and authorization tests

The cross-space listings now map a member's snooze, clearing a chat
keeps snoozes through an update the mocks didn't offer, and snoozing a
chat outside one's space is denied like marking it unread.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Keep snoozes across spaces and put failed snoozes back

Other spaces' chats now carry their snooze in the space list, and the
web Snoozed shelf lists every space's snoozed chats, unsnoozing through
that space. A snooze the server rejects puts the chat back and says so,
even if the reload after it fails. Mobile rows redraw when a chat wakes,
Mark as Read clears a woken group's label on web, and both apps name a
wake day by one shared rule.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Read a chat that wakes while open, and snooze from the click

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Leave snoozed chats out of the pushed badge count

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Send a chat's snoozes in order, and name a wake's weekday by calendar days

A snooze and a quick unsnooze reach the server one after the other, and
only the latest one rolls back on failure. A wake six calendar days away
shows its weekday, whatever the hours or a clock change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Reload the mobile inbox once a chat's snoozes are sent

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Wake a chat whose snooze passed before the roster brought it in

The snooze clock measures the next wake from the time the roster reads,
so a wake that passed since it last moved ticks at once. A failed mobile
snooze puts the chat back to what the server last saved.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh after a chat's latest web snooze, and roll back to what was saved

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Open a listed chat by default, and head the shelves with SectionHeading

With no chat in the route, web opens one that's in the list rather than a
snoozed one, so a chat isn't read, and its snooze ended, without being
chosen. The Snoozed and Archived shelves use the shared section heading.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Keep snoozes in the saved mobile inbox, and cap open shelves together

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Keep snoozed chats out of Activity, and offer Snooze on its rows

Activity's sections leave a snoozed chat out until it wakes, as the chat
list does, so a snoozed bot waiting on an answer no longer shows under
Needs attention. Activity rows on iOS offer Snooze like inbox rows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Hold a snoozed chat's news until it wakes

Replies and teammates' messages no longer push or alert a member who has
the chat snoozed. Questions, takeovers and failures still reach the
person whose run it is, since the chat is hidden from them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Retry a snooze lookup that fails during a retried delivery

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Share one snooze queue across web and …
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants