stablyai / stablyai/orca

[Bug][Mobile]: New-tab agent menu resolves detection from repo.connectionId only, so runtime-hosted workspaces list the paired host's agents

Open
#13,752 3 comments 0 reactions 1 assignee Claimed by @brennanb2025 View on GitHub
Dominant language
TypeScript
Stars
72.1k
Forks
4.7k
Avg merge
14h 54m
Merged PRs (30d)
520

Description

## Summary

The mobile new-tab agent menu resolves the agent-detection host from `repo.connectionId` only. Runtime-hosted repos have `connectionId: null`, so they fall through to `preflight.detectAgents`, which runs on **the paired host** rather than on the runtime environment that owns the workspace. The menu then lists the paired host's installed agents; agents installed only on the runtime host never appear, and when the paired host has no agents at all the section is empty.

This is the mobile counterpart of #9370, which was fixed for desktop by #9790 (`AgentsPane` + `QuickLaunchButton`). The mobile loader was never extended, so it still has the SSH-only resolution that #9790 replaced.

Code references are from `main` @ `c065a2d` (2026-08-10).

## Where it goes wrong

`mobile/src/session/mobile-new-tab-agent-loader.ts` declares a repo shape that cannot express runtime ownership:

```ts
// :11-14
type RuntimeRepoSummary = {
id: string
connectionId?: string | null
}
```

and then branches on that field alone:

```ts
// :58-61
const connectionId = repo.connectionId?.trim() || null
return connectionId
? client.sendRequest('preflight.detectRemoteAgents', { connectionId })
: client.sendRequest('preflight.detectAgents') // runtime-hosted repos land here
```

`repo.list` returns full `Repo` rows (`src/main/runtime/rpc/methods/repo.ts:109-114` → `runtime.listRepos()`), so the field that disambiguates this case is already on the wire — and its doc comment names this exact failure mode:

```ts
// src/shared/types.ts:269-273
/**
* Explicit execution owner for this repo. Runtime-host repos need this
* because they otherwise look identical to local repos (`connectionId: null`).
*/
executionHostId?: 'local' | `ssh:${string}` | `runtime:${string}` | null
```

Desktop resolves all three owners from that field:

```ts
// src/renderer/src/hooks/useAgentDetectionTarget.ts:53-60
const executionHost = parseExecutionHostId(getExecutionHostIdForWorktree(state, worktreeId))
if (executionHost?.kind === 'ssh') {
return `ssh:${executionHost.targetId}`
}
if (executionHost?.kind === 'runtime') {
return `runtime:${executionHost.environmentId}`
}
return AGENT_DETECTION_LOCAL_TARGET_KEY
```

The mobile loader has no `runtime` branch, and the fallback is silent — there is no signal distinguishing "the runtime host reports no agents" from "we asked the wrong host".

## Steps to reproduce

1. Run `orca serve` on a headless host **B** and install an agent there that is **not** installed on your desktop **A** (e.g. `pi`).
2. On desktop **A**, register **B** as a Remote Orca Server environment and open a workspace that lives on **B**.
3. Pair Orca Mobile (Android or iOS) to **A**.
4. In the mobile app, open that workspace and tap `+` to open the new-tab drawer.

**Actual:** the agent section lists **A**'s installed agents. `pi` (installed only on **B**) is absent. If **A** has no agents installed, the section is empty.

**Expected:** the agent section lists the agents installed on **B**, matching what desktop **A** shows for the same workspace after #9790.

## Note on the fix

The loader cannot fix this on its own. `preflight.detectAgents` takes no params (`src/main/runtime/rpc/methods/preflight.ts:28-31`, `params: null`) and mobile's `RpcClient` addresses only the single paired host (`mobile/src/transport/rpc-client.ts:972`), so there is currently no way for mobile to target a specific runtime environment. Desktop sidesteps this because its renderer dials each environment directly (`src/renderer/src/store/slices/runtime-detected-agents.ts:69-72` via `callRuntimeRpc({ kind: 'environment', environmentId })`).

Making mobile correct therefore needs a host-side delegation step. The transport already exists — `callRuntimeEnvironment(userDataPath, selector, method, params, ...)` (`src/main/ipc/runtime-environment-transport-routing.ts:78`) — but `rpc/methods/` does not currently perform environment delegation anywhere (no `userDataPath` usage in that layer), so which layer should own it, how to prevent delegation loops, and whether a mobile client may name an arbitrary environment all look like maintainer calls rather than something to settle in a drive-by patch. Filing this instead of a PR for that reason.

Two smaller things worth folding in whatever the shape of the fix:

- `executionHostId` should be added to `RuntimeRepoSummary` and preferred over `connectionId`, so the resolution cannot drift from desktop again.
- Any failure in the loader currently blanks the whole section: `loadMobileNewTabAgentOptions` throws on a `repo.list`/`settings.get` failure or on `worktree_repo_not_found` (`mobile-new-tab-agent-loader.ts:29-34, 55-57`), and the call site maps that to an empty list (`mobile/app/h/[hostId]/session/[worktreeId].tsx:3747-3751`). A wrong-host result and a hard failure are indistinguishable to the user.

Existing coverage in `mobile/src/session/mobile-new-tab-agent-loader.test.ts` has only the floating-workspace (local) and SSH cases — a runtime-owned case would be the natural regression test.

## Related

- #9370 — desktop new-tab menu listed local agents for runtime-hosted workspaces (closed as a duplicate of a consolidated investigation)
- #9790 — fixed the desktop surfaces (`AgentsPane`, `QuickLaunchButton`)
- #9373 — open PR extracting the shared desktop resolution hook
- #3290 / #3334 — added agent presets to the mobile new-tab drawer in the first place
- #11869 — mobile not surfacing remote runtime workspaces (same "mobile can't see the runtime host's data" family)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.