Skip to content

fix(first-run/runtime): clean two-option onboarding chooser + runtime/instance-management hardening - #11509

Merged
lalalune merged 7 commits into
developfrom
fix/onboarding-cloud-error-and-runtime-options
Jul 2, 2026
Merged

lalalune merged 7 commits into
developfrom
fix/onboarding-cloud-error-and-runtime-options

Conversation

@lalalune

@lalalune lalalune commented Jul 2, 2026

Copy link
Copy Markdown
Member

Onboarding cleanup + runtime/instance-management hardening

Cleans up the in-chat onboarding runtime chooser and fixes two bugs a deep-scan
surfaced in the shared runtime/instance-management layer. All six changes were
verified on a real Pixel 6a
(screenshots below), and every change ships real
tests (jsdom + real localStorage — no mock standing in for the thing under test).

What changed

  1. Friendly cloud-connect error. A transport failure (offline/DNS/timeout) in
    getCloudCompatAgents() stuffed the raw err.message
    (Unable to resolve host "api.elizacloud.ai"…) into the onboarding chat. A
    thrown error there is always transport (the client returns {success:false}
    for real API errors), so surface Couldn't reach Eliza Cloud — check your internet connection and try again. The re-offered runtime choice keeps the
    flow recoverable.
  2. Prominent, obviously-tappable onboarding choices. ChoiceWidget first-run
    scope renders full-width stacked rows with a chevron + ✓-on-select
    (were tiny low-contrast chips). The single (recommended) option carries the
    accent; the rest are neutral (one orange accent = brand rule). Other CHOICE
    scopes unchanged.
  3. Directive frozen-composer hint. During onboarding the composer placeholder
    is now Tap a highlighted option above to continue at higher contrast (was a
    greyed-out Choose an option to continue).
  4. Clean two-option runtime chooser (Cloud + On this device). Removed
    runtime:other=“Bring your own keys” — it was never a location (it ran the
    local backend and only pre-highlighted the BYOK inference provider). BYOK stays
    reachable one step later via provider:otherconfigure-later → Settings
    banner (no silent model download). Remote + multi-instance management live in
    Settings → Runtime (per first-run: replace the full-screen onboarding gate with an in-chat login + runtime + provider flow (agent greets first) #9952). Also de-duped the re-offered choice to reuse
    RUNTIME_CHOICE.
  5. Cross-surface state-drift fix. applyLaunchConnection (remote-connect +
    cloud launch-session) and CloudAgentsSection.bindAndReload (cloud-agent
    switch) wrote only elizaos:active-server and never the agent-profile registry
    that “My Runtimes” reads — so an instance connected/switched in Settings was
    invisible there with a stale Active badge. New idempotent
    upsertAndActivateAgentProfile() (find by kind+apiBase → re-activate + refresh
    token/label; else add) routes both write-paths through it — reconnecting the
    same host never duplicates.
  6. Onboarding sheet pinned open. On a home-view boot the chat sheet could
    settle collapsed during onboarding — the frozen composer said “tap an option
    above” while the greeting + choices sat hidden behind the grabber (openness was
    derived from a raceable effect). Pinned structurally: while firstRunOpen the
    derived openness is always FULL via effectiveMode.

Evidence

  • On-device (Pixel 6a, Android 16): clean two-option chooser, prominent rows +
    chevrons + ✓, directive hint, friendly cloud error (device was offline — exactly
    the path this fixes), and the sheet auto-opens so options show without a manual
    swipe. Captures in .github/issue-evidence/onboarding-runtime-cleanup/.
  • Tests: 66/66 across the 6 touched suites; ui typecheck 0 errors; biome clean.
  • Real-LLM trajectories: N/A — no agent/action/provider/prompt/model behavior
    changed (onboarding UI + client-side instance registry only).

Deferred (genuine product/security decisions — filed as needs-human issues)

Remote trust-bar unification, Settings IA consolidation (3 overlapping runtime
surfaces), destructive-vs-in-place cloud/local switch, deep-link-remote persistence.

🤖 Generated with Claude Code

lalalune and others added 6 commits July 2, 2026 12:00
…raw UnknownHostException

When the device is offline (or DNS/timeout), getCloudCompatAgents() throws and
its raw err.message ('Unable to resolve host "api.elizacloud.ai": No address
associated with hostname') was stuffed into the error outcome and seeded
verbatim into the onboarding chat. A thrown error there is always a transport
failure (the client returns { success:false } for real API errors), so surface
a friendly, actionable line. The re-offered runtime choice already makes the
flow recoverable. Test updated to guard the friendly message.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…en-composer hint

- ChoiceWidget: first-run scope renders full-width stacked rows with a chevron
  affordance instead of compact inline chips, so the options read as obvious
  next-step targets. The single '(recommended)' option carries the accent;
  the rest are prominent neutral (one orange accent = brand rule). Other CHOICE
  scopes (mid-conversation disambiguation) are unchanged.
- Composer: while onboarding freezes the input, the placeholder is now the
  directive 'Tap a highlighted option above to continue' at 70% opacity (was a
  greyed-out 45% 'Choose an option to continue' that read as dead).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…vice)

Remove the 'Bring your own keys' (runtime:other) button from the onboarding
'where should your agent run?' chooser. It was never a location — it ran the
local backend and only pre-highlighted the BYOK inference provider, so it lived
on the wrong axis and confused the choice. BYOK stays fully reachable one step
later via the provider sub-choice (provider:other → localInference
'configure-later' → Settings handoff banner, no silent model download). Remote
agents + multi-instance management live in Settings → Runtime (post-#9952).

Also de-dupe the re-offered runtime choice in seedError to reuse RUNTIME_CHOICE
instead of an inline copy (the two had already drifted). Tests: rewrite the
BYOK-path test to assert reachability via runtime:local → provider:other, add a
two-buttons-only assertion + stale-runtime:other-is-a-no-op guard, and keep
runtime:other in the fuzz storm as a retired no-op.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t/switch (My Runtimes was going stale)

Cross-surface state drift: applyLaunchConnection (the remote-connect + cloud
launch-session path) and CloudAgentsSection.bindAndReload (cloud-agent switch)
wrote ONLY elizaos:active-server and never touched the agent-profile registry
(elizaos:agent-profiles) that 'My Runtimes' reads. So a remote connected in
Settings, or a cloud agent switched in 'Eliza Cloud Agents', was invisible to
the runtime switcher and left its Active badge stale — even though onboarding
already seeds profiles for local/cloud.

Add upsertAndActivateAgentProfile(): find by (kind, apiBase) → re-activate +
refresh token/label (idempotent — reconnecting the same host never duplicates,
and a re-activate that carries no token keeps the prior one); otherwise add +
activate. Route both write-paths through it alongside the active-server write.

Real tests (jsdom + real localStorage): upsert add/idempotent/distinct-host/
token-preservation, and a browser-launch assertion that applyLaunchConnection
syncs the registry. 12/12 green; ui typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ere hidden behind a collapsed grabber)

On a home-view boot the sheet could settle COLLAPSED during onboarding — the
frozen composer showed 'Tap a highlighted option above to continue' while the
seeded greeting + Cloud/On-this-device choices sat hidden behind the grabber,
because openness was derived from a raceable mount/effect path. Pin it
structurally: while firstRunOpen, the derived openness (pilled/sheetOpen/
expanded) is always FULL via an effectiveMode, independent of the underlying
mode-transition race. The existing pin-full effect still drives the real mode so
the falling edge (onboarding complete) auto-collapses correctly. Regression test
asserts chat-sheet data-variant='open' + chat-thread present under firstRunOpen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0325f671-7ef8-46a1-87b7-4e62dbe30f34

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/onboarding-cloud-error-and-runtime-options

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…e cleanup

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@lalalune
lalalune merged commit d534de0 into develop Jul 2, 2026
27 of 63 checks passed
@lalalune
lalalune deleted the fix/onboarding-cloud-error-and-runtime-options branch July 2, 2026 19:03
@lalalune

lalalune commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

Merged ✅ — verified on develop (d534de09f3). The four deferred product/security decisions (remote trust bar, settings IA consolidation, cloud/local switch semantics, deep-link-remote persistence) are filed for a human call in #11511 (needs-human).

lalalune added a commit that referenced this pull request Jul 2, 2026
#11509) (#11656)

PR #11509 removed the third runtime chip (`runtime:other` = "Bring your own
keys") from the in-chat onboarding conductor — it conflated the inference-
provider axis with the runtime-location axis. Four ui-smoke specs still asserted
that deleted chip is VISIBLE, so they are guaranteed-red on develop:
- runtime-configurability.spec.ts
- first-run-startup.spec.ts
- walkthrough/walkthrough-capture-smoke.spec.ts
- walkthrough/journey.ts (step 02)

Each now asserts the chip is absent (`toHaveCount(0)`) instead of visible, and
the stale header/expectation comments + the IN_CHAT_ONBOARDING_DESIGN.md option
list are corrected to the shipped Cloud/Local chooser. The conductor seeds only
runtime:cloud + runtime:local (use-first-run-conductor.ts:108-113); BYOK remains
reachable as provider:other one step later. The unit tests that assert a stale
runtime:other pick is consumed as a no-op are correct and left unchanged.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
lalalune added a commit that referenced this pull request Jul 3, 2026
…dings (#11509 #11506) (#11710)

On-device (Pixel 6a) evidence for the Android onboarding review:
- BEFORE (stale 12:56 build): the 3-option runtime chooser WITH 'Bring your own
  keys' (02-...-symptom2), the Custom-Tab cloud sign-in (00-current-state), and
  onboarding resetting after the #11506 process restart (07-...).
- AFTER (fresh develop build, agent-bundle staged, verified lastUpdateTime):
  boots straight to home with a working local agent, no chooser, no BYOK chip,
  pid stable >2min (no #11506 churn in-window) — 01-fresh-develop-boots-to-home.
- FINDINGS.md: all four reported symptoms root-caused with file:line.

Honest caveats documented: this build omits local model inference (fused lib /
omnivoice FFI header not in the worktree); the cloud-sign-in persistence bug is
Settings-gated on develop and not exercised here.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
lalalune pushed a commit that referenced this pull request Jul 3, 2026
…d + locked-composer placeholder

The onboarding-to-home ui-smoke suite (10 specs, desktop + mobile) had been
fully red: the shared helper still asserted the removed runtime:other choice
(the chooser is now cloud/local/remote — #11509) and the old locked-composer
placeholder 'Choose an option to continue' (shipped text is 'Tap a highlighted
option above to continue'). Point the runtime assertion at 'remote', reach the
provider:other handoff via runtime local, and match the real placeholder. All
10 pass — cloud + local + cloud-inference + other-provider + remote + tutorial,
desktop and mobile touch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lalalune added a commit that referenced this pull request Jul 3, 2026
…n choices to a11y (#11899)

* fix(ui): show onboarding greeting immediately — stop gating it on the agent-wake probe

Fresh-install onboarding stranded the user at a locked composer ('Tap a
highlighted option above to continue') with NO visible choices whenever the
local agent was slow/wedged to wake: the conductor only seeded the greeting +
runtime choice inside the then()/catch() of client.listLocalAgentBackups(),
which hits the still-booting agent API and can hang indefinitely.

Now the greeting + runtime choice seed synchronously on mount, independent of
agent readiness; the local-backup probe is a purely additive upgrade that
appends the restore choice below the greeting only if backups exist AND the
user hasn't already advanced past it (atomic updater, no double-seed). Two new
conductor tests cover the never-settling probe (greeting still appears + a
runtime pick works) and the racing-late-backups guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(a11y): expose in-chat first-run choices to the accessibility tree + add onboarding probe

The assistant message bubble in ContinuousChatOverlay's ThreadLine wrapped its
content in role=button aria-label="Show message actions" whenever it had
row-actions. WebKit maps an ARIA button to an ATOMIC accessibility leaf (its
aria-label becomes the node name and ALL descendants are dropped), so the
onboarding greeting text and every ChoiceWidget button vanished from the native
AX tree: unreachable by VoiceOver AND by on-device XCUITest (only the collapsed
"Show message actions" node was exposed).

Fix: a choice/form/followups-bearing assistant turn (every first-run onboarding
turn) now stays a plain container -- bubbleInteractive gains a
!hasInteractiveWidget guard (detected via the existing choice/form/followups
parsers). Normal turns keep the tap-to-reveal Copy/Play/Edit row unchanged.
Also adds an sr-only onboarding-state-probe (mirroring chat-detent-probe /
home-launcher-page-probe) exposing the current first-run step id + choice ids
so on-device automation can observe/drive onboarding deterministically. Two new
unit tests cover both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(onboarding): cloud login no longer restarts at the greeting — resume into chat after WebView eviction

On a native device the first-run cloud login opens an external browser
(SFSafariViewController) that backgrounds the WebView; iOS frequently
cold-launches the app on return, wiping the conductor's in-memory flow state and
the volatile __ELIZA_CLOUD_AUTH_TOKEN__ global. The result was a "restart": the
user was bounced back to "where should your agent run?" instead of landing in
chat.

Two durable channels close the gap:
- Persist the cloud token to the steward-session store (getCloudAuthToken reads
  it first) on poll/direct auth success, so elizaCloudConnected recomputes true
  after relaunch.
- A cloud-login resume marker (localStorage) armed at cloud/hybrid pick time and
  cleared on completion or a fresh runtime re-pick. On relaunch the conductor
  rehydrates the interrupted flow (draft + pending-resume) and continues into
  chat instead of re-seeding the greeting.

Fixes the effect-ordering trap where the auto-resume effect fired before the
mount effect armed the marker: the resume body is extracted into runCloudResume
and kicked directly when the connection is already live at mount.

Tests: first-run-cloud-resume unit (round-trip/validation), conductor
regressions (marker armed on pick, resume-on-relaunch with no greeting restart,
cleared on fresh local pick). 26 pass; typecheck + biome clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vq3VfLC939jy9e4aA12KGP

* fix(ios-local): serve POST /api/first-run in the JSContext kernel — fixes local onboarding "not found" loop

On a dev iOS device the local agent runs through the JSContext compatibility
kernel (ios-local-agent-kernel.ts), which implemented GET /api/first-run/status
but had NO POST /api/first-run handler. finishLocal's submitFirstRun POST fell
through to the catch-all 404 ("Not found"); the conductor's seedError turned
that into a re-offer of the "where should your agent run?" runtime chooser — the
on-device "local path → not found → pick the targets again" loop the user hit.
(The full-Bun store build + the mocked web e2e both serve the route, which is
why neither caught it.) Add the handler: accept + ack the finish payload
(no-op success matching the full-Bun bundle). Regression test asserts the
kernel serves it 200 instead of 404.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): restore onboarding→home e2e — align stale runtime choice id + locked-composer placeholder

The onboarding-to-home ui-smoke suite (10 specs, desktop + mobile) had been
fully red: the shared helper still asserted the removed runtime:other choice
(the chooser is now cloud/local/remote — #11509) and the old locked-composer
placeholder 'Choose an option to continue' (shipped text is 'Tap a highlighted
option above to continue'). Point the runtime assertion at 'remote', reach the
provider:other handoff via runtime local, and match the real placeholder. All
10 pass — cloud + local + cloud-inference + other-provider + remote + tutorial,
desktop and mobile touch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Shaw <shawgotbags@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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