Skip to content

Release Relay 0.1.5 - #12

Merged
elin66alpha merged 12 commits into
mainfrom
0.1.5
Aug 19, 2026
Merged

Release Relay 0.1.5#12
elin66alpha merged 12 commits into
mainfrom
0.1.5

Conversation

@elin66alpha

Copy link
Copy Markdown
Owner

Summary

  • keep Claude Code, Codex, OpenCode, and Hermes conversations in persistent,
    resumable sessions instead of spawning a process per turn
  • add bounded agent-to-agent @mention waves in Swarms, credential-expiry
    visibility, faster option/quota loading, and reliable search-result jumps
  • remove Antigravity, BTW side conversations, and the in-app OAuth bridge so
    all remaining agent credentials stay host-managed
  • add Linux service lifecycle scripts, CI, expanded backend security/policy
    tests, an MIT license, and refreshed English/Chinese documentation

Release

Validation

  • flutter analyze --no-pub
  • flutter test --no-pub — 87 tests passed
  • JavaScript syntax check across all tracked backend scripts
  • npm --prefix server test — 214 tests passed
  • flutter build apk --release --no-pub
  • APK package/version inspection and APK Signature Scheme v2 verification
  • backend setup archive sensitive-path, executable-mode, shell syntax, and
    JavaScript syntax audit

Merge notes

  • This intentionally removes stored/visible Antigravity and BTW functionality.
  • Agent authentication must now be completed directly on the backend host.
  • The published Android APK is release-optimized but debug-signed because a
    production keystore is not configured yet.

AgentDeck and others added 12 commits July 26, 2026 00:55
Claude's five-hour window only exists while it runs. Once it lapses the
usage API reports no reset time, so the usage screen can only show
"unknown" until the next real turn. Codex avoids this because its quota
probe is itself a live request; Claude's is a plain read.

Send the equivalent minimal Claude Code turn ourselves whenever the
window is idle (cheapest model, one output token, same OAuth credential
as the usage query), then sleep until just after the new reset moment so
the cycle repeats on its own. Never ping on stale usage data, and keep a
floor between two pings so a ping that fails to open a window cannot turn
into a retry loop.

Enabled by default; ENABLE_CLAUDE_KEEPALIVE=false turns it off.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Closes the gaps found in a workspace and documentation review.

- Test the file API access policy, the device-token store, and quota
  schedules (server suite: 136 -> 190 tests). tokens.js and
  quota-schedules.js take RELAY_*_FILE overrides so the modules are
  testable without touching deployment state, following the existing
  RELAY_HISTORY_FILE pattern. The file API deny list now reads the token
  path from tokens.js instead of assuming the default location, so the
  new override cannot move the token store out from under it.
- Add an MIT LICENSE and a GitHub Actions workflow running the analyzer
  and both test suites on pull requests.
- Add Linux start/stop/status/uninstall scripts so all three backend
  operating systems have the same entry points; uninstall removes the PM2
  processes and leaves backend data in place.
- Ignore release artifacts so a stray `git add -A` cannot commit an 80 MB
  APK.
- Bump to 0.1.5 across pubspec.yaml, server/package.json, the settings
  screen constant, and CHANGELOG.md, and record the four locations in
  AGENTS.md so they stop drifting apart.
- Document the remaining supported environment variables, and correct the
  claim that the credential passphrase is only ever entered interactively.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Removes the Antigravity (agy) agent from the client: its model entry, icon,
login flow, drawer entry, strings, and tests. SECURITY.md and the backend
READMEs drop it from the login-bridge and credential wording.

Quota alerts are sent twice on purpose — down the event stream for open
sessions and as a push for closed ones — and on the web the service worker
shows its copy regardless of focus, so one alert could appear twice.
Notifications now carry a tag derived from the alert, and a repeat replaces
the first instead of stacking.

Credential QR import prefers the platform's own image decoder and falls back
to the pure-Dart pipeline on a background isolate. Running the Dart decode on
the web blocked the only thread there is, freezing the tab past the point
where the scan timeout could still fire.

Also stops the agent drawer tile from hiding its ink splash behind a plain
decoration, and adds the Flutter migrator's Android Kotlin/DSL flags.
Relay ran one CLI process per turn, so anything an agent started in the
background died the moment the turn ended, and every turn paid the CLI's cold
start. Each agent now keeps a live session that turns are fed into, the way a
terminal session does.

- Claude Code uses the Agent SDK's streaming-input mode: one process per
  conversation (claude-session-pool.js).
- OpenCode, Hermes and Codex speak line-delimited JSON-RPC on stdio — `acp`
  for the first two, `app-server` for Codex. stdio-agent-pool.js owns the
  process, the wire, the session cap, idle eviction and cancellation;
  acp-session-pool.js and codex-session-pool.js supply the protocols. One
  process per agent hosts every chat, since each session carries its own
  workdir, so a large startup cost is paid once instead of once per chat.

Cold turn to warm turn, measured locally: 3.1s -> 1.5s (claude), 3.9s -> 1.4s
(opencode), 5.2s -> 1.2s (hermes), 3.7s -> 1.4s (codex). Cancelling a turn now
interrupts it instead of killing the conversation, and OpenCode and Hermes
replies stream token by token, which neither could do before.

Every pool is a cache: the session id in agent-sessions.json stays
authoritative, so a scope without a live session cold-starts by resuming it and
behaves exactly as before. Idle sessions close and a cap bounds memory
(RELAY_CLAUDE_*, RELAY_AGENT_*); turns past the cap wait for a slot.

Background work started by a turn now outlives it, except on Codex, whose
sandbox kills each command's process group as the command returns — there it
survives only if it detaches with setsid.

Deleting or clearing a conversation now deletes the CLI-side transcript, so a
deleted conversation cannot be resumed and does not linger on disk. Codex's
/btw fork uses the CLI's own thread/fork instead of copying rows and rollout
files inside ~/.codex/state_5.sqlite, removing that version-specific surgery.

Approval requests reach Relay directly now; until there is an approval UI the
bypass tiers approve them and the cautious tiers refuse, which is deterministic
where the old non-interactive runs could stall.

Also completes the server-side Antigravity removal, and ignores *.bak under
server/ so backups of the state files stay as uncommittable as the originals.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The restart script polled /api/health unauthenticated, but that route sits
behind requireAuth, so it could only ever get 401 — the script reported a
failed restart every time even though the backend was up. Worse, retrying
spent the brute-force guard's budget of 15 auth failures per minute, so the
last few attempts came back 429.

Any HTTP status now counts as "the process is listening and Express is
serving"; only a connection failure or a 5xx keeps the loop waiting.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BTW existed only for Claude Code and Codex, and only to ask a read-only side
question against a fork of the main conversation. Removing it drops the
/api/btw routes, the `btw:<agent>` session scopes and their transcripts, the
BTW button and dialog in the app, and the per-agent session forking that
existed solely to support them:

- Claude's runner had been split into runClaudeInvocation + runClaude so BTW
  could reuse it with forkSession/canRetry; with one caller left they collapse
  back into runClaude, and the pool loses its forkSession option.
- Codex loses thread/fork and the stdio pool loses driverCall, the seam added
  to reach it.
- Deleting or clearing a chat no longer has to take a derived side scope down
  with it.

No stored state needed migrating: there were no btw session ids and no btw
history scopes left on this machine.

Verified after the removal that all four agents still open, resume and purge a
normal chat, and that a follow-up turn still carries memory on the same
session id.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replace the in-app OAuth login bridge with a credential countdown. Relay
never logs a CLI in now: `/api/agents` reports `credentialExpiresAt` read
from the timestamps Claude Code and Codex already store beside their
tokens, and Manage credentials shows the days left or the days since
expiry instead of a Log in button. Drops `/api/agent-auth/*`, the backend
PTY that drove `script -qfec`, and the login dialog, so the backend no
longer starts a process on behalf of a client.

Take the subprocesses off the option hot path. Every
`/api/agent-options`, `/api/agent-settings`, and agent turn re-located the
CLI with a synchronous `command -v` and re-read models-extra.json,
blocking the event loop (and every SSE stream) for ~6 ms, ~12 ms for a
settings read. Discovery now re-checks the binary at most once a minute
and remembers absent CLIs; models-extra.json is cached by mtime; a CLI
update still busts both at once. describeAgent 6.1 ms -> 0.04 ms,
getSettings ~12 ms -> 0.03 ms. `<cli> --version` is cached the same way.
Client side, the composer caches the per-agent catalog so the "+" panel
renders at its final size instead of spinning then growing, and the quota
screens paint the last report while refreshing behind it.

Let swarm members summon each other. A round now runs in waves: an
`@mention` in a member's reply hands that teammate the floor, and each
wave re-snapshots the transcript so the summoned member sees what was
just said. Member prompts list their teammates and the @name that
reaches each, since a member that does not know summoning works will
never use it. RELAY_SWARM_MAX_HOPS bounds the agent-driven waves after
one human message (default 3, 0 keeps it human-only), a member cannot
summon itself, and a failed or cancelled turn summons no one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Picking a "Search chats" result only called selectSession, which switched
the conversation but left the view on the newest message with nothing
marked. The messageId the backend already returns was dropped, and the
agents controller kept pointing at the old agent, so the next context
sync could load that agent's conversation back over the jump.

The hit now moves the active agent as well as the chat controller, then
scrolls the (reversed, lazily built) list to the matched message by
walking toward it on the scroll position's own average-extent estimate
until the row mounts and ensureVisible can centre it. The row flashes a
tint and the search term is marked inside the bubble for a couple of
seconds; the markdown path marks it via a custom inline syntax so the
surrounding formatting still renders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@elin66alpha
elin66alpha merged commit fd3195a into main Aug 19, 2026
2 checks passed
@elin66alpha
elin66alpha deleted the 0.1.5 branch August 19, 2026 02:04
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.

1 participant