Skip to content

fix(mobile): detect agents inside the workspace's WSL distro (New Tab + New Workspace) - #20101

Closed
welldoingk wants to merge 4 commits into
stablyai:mainfrom
welldoingk:fix/mobile-wsl-agent-detection-create-workspace
Closed

welldoingk wants to merge 4 commits into
stablyai:mainfrom
welldoingk:fix/mobile-wsl-agent-detection-create-workspace

Conversation

@welldoingk

@welldoingk welldoingk commented Sep 11, 2026 •

Copy link
Copy Markdown

Consolidates #19886 (New Tab drawer + host/web plumbing) and the former #20101 follow-up (New Workspace surfaces) into one PR, rebased on current main (the mobile session/tasks readers moved to typed RpcOperations since the originals were opened). #19886 is closed in favour of this one.

ELI5

If your workspaces live inside WSL, your phone's New Tab drawer said "No Enabled Agents" — not even Claude or Codex — while the desktop app listed them all for the same workspace. The desktop knows how to look inside the distro because it tells the detector which distro to check; the phone had no way to say that, so the host looked at the Windows side of the machine, where a WSL-first user has no agents installed. Now the request can name the distro, and hosts that never heard of that field keep behaving exactly as before. The paired web client had the same hole one layer up — its copy of the preflight API took no arguments at all, so the distro its own callers already passed was thrown away before the request left the browser — and that is fixed here too.

What Changed

  • src/main/runtime/rpc/methods/preflight.ts: preflight.detectAgents and preflight.refreshAgents swap params: null for an optional schema carrying wslDistro / wslDefault, mapped to the PreflightRuntimeContext the Electron IPC twin already accepts. No target named still means host-local, so getPreflightWslTarget keeps distinguishing "no context" from "context that selected nothing". The wslDistro constraint copies the one accounts.ts already uses for the same value.
  • mobile/src/session/mobile-new-tab-agent-loader.ts: the local branch reads the distro out of the workspace's own path and sends it. SSH/connection resolution is untouched.
  • mobile/src/session/mobile-session-route-helpers.ts: getWorktreePathFromMobileWorktreeId, the path half of the existing getRepoIdFromMobileWorktreeId. Parsing reuses shared parseWslUncPath rather than a second copy.
  • src/renderer/src/web/preload-api/web-host-capability-api.ts: the web twin of the preload API declared detectAgents: async () => and refreshAgents: () =>, so the context its callers pass — refreshAgents(context) in local-detected-agent-state.ts and detectAgents({ wslDistro }) in skill install — was dropped before the call. A zero-arg function is assignable to the optional-arg signature, so this never failed typecheck. Both now accept and forward it.
  • src/renderer/src/web/preload-api/web-preflight-detect-params.ts (new): forwarding the raw context would regress rather than fix, because it carries wslDistro: string | null plus projectRuntime, and the wire schema rejects null. This maps it with the host's own getPreflightWslTarget precedence (resolved project runtime, then explicit distro, then the default flag), and isPreflightRepairRequired skips the probe entirely for a runtime that needs repair, matching the desktop's refusal instead of listing Windows-side agents a WSL-pinned project cannot launch.
  • src/shared/rpc-contract/rpc-params-catalog.generated.ts: regenerated, since the new schema lives in the directory generate-rpc-params-catalog.mjs scans and verify:rpc-params-catalog gates it.
  • Tests: 3 new RPC cases (context forwarded, malformed target rejected before probing, no params still means host-local), 2 new loader cases (WSL-hosted workspace, host-filesystem workspace), and 10 new web-client cases covering the mapping and the wiring itself — the ones that pin forwarding would fail against the old zero-arg implementation.

New Workspace surfaces (was #20101)

  • mobile/src/worktree/workspace-agent-detection-target.ts (new): getWorkspaceDetectAgentsParams(repoPath) turns a repo path into the detection target, reusing the shared parseWslUncPath. A non-WSL path yields undefined, so the request stays byte-identical to today's.
  • mobile/src/components/use-new-workspace-execution-target.ts: takes an optional repoPath and names the distro on the local branch. The SSH branch (preflight.detectRemoteAgents) and the connection gate are untouched.
  • mobile/src/components/NewWorktreeModal.tsx: passes selectedRepo?.path, the repo the modal already selected.
  • mobile/src/tasks/use-mobile-tasks-workspace-ssh-state.tsx: the workspace-create draft uses workspaceCreateTargetRepo.path, which the hook already holds.
  • mobile/src/tasks/mobile-tasks-refactor-parity.test.ts: three parity hashes refreshed, since that ratchet covers the whole mobile/src/tasks source tree. Same maintenance Preserve user-set workspace names across branch changes #17448 did for a behavioural change.

getPreflightWslTarget(undefined) returns null on win32, so a request with no target probes the Windows PATH. The New Tab loader could read the distro out of an existing worktree id (${repoId}::${path}); these two surfaces run before that id exists, which is exactly why they were left out of #19886 and filed separately.

// before — both call sites
: await client.sendRequest('preflight.detectAgents')

// after
: await client.sendRequest('preflight.detectAgents', getWorkspaceDetectAgentsParams(repoPath))

Why

The two entry points into agent detection are twins, but only one could be told where to look:

// src/main/ipc/preflight.ts:31 — desktop, carries the context
ipcMain.handle('preflight:detectAgents', async (_event, args?: PreflightRuntimeContext) =>
  detectInstalledAgentsWithShellPathHydration(args)
)

// src/main/runtime/rpc/methods/preflight.ts:29 — paired clients, before this PR
defineMethod({ name: 'preflight.detectAgents', params: null,
  handler: async () => detectInstalledAgentsWithShellPathHydration() })

getPreflightWslTarget(undefined) returns null on win32, so the probe ran against the Windows PATH. On a WSL-first machine that PATH holds no agent CLI, so the drawer was empty. The desktop never hit this because getLocalAgentPreflightContext derives the distro from the workspace path.

Reading the distro from the route rather than adding a resolver: a worktree id is ${repoId}::${path}, the path is the host's own spelling of the workspace, and parseWslUncPath is the existing answer for "which distro is this path in". A non-WSL path yields null and the call goes out with no params, unchanged.

Linked Issue

Fixes #19885
Fixes #20060

Visual Proof

Android client paired to a Windows host, WSL workspace, New Tab drawer. Both shots keep the workspace header, the tab bar, and the drawer; the terminal area between them is cropped out because it was showing unrelated session content.

BEFORE — stock client + host: the agent section is a single disabled "No Enabled Agents" row, while Terminal / Browser / Markdown Note below it work fine.

before

AFTER — patched client + host: Claude, Codex, OpenCode, Antigravity, Hermes. Gemini and Pi are absent because this profile has them in disabledTuiAgents, which is the correct filtering, not a miss.

after

Testing

Verified on a real device against a real host, not only in unit tests.

1. Live host RPC, stock vs patched, same machine, same minute. Both apps were queried over their own runtime named pipe (the transport the bundled CLI uses):

params Orca 1.4.199 (installed) this branch (dev build)
none [] []
{"wslDistro":"Ubuntu-24.04"} [] ["claude","codex","opencode","pi","gemini","antigravity","hermes"] (824ms)
{"wslDistro":"NoSuchDistro-xyz"} [] [] (88ms)
{"wslDistro":""} [] invalid_argument: Too small: expected string to have >=1 characters

Row 2 on the left is the bug; row 2 on the right is the fix, and its 824ms is a real wsl.exe round trip. Row 3 is the discriminator: a distro that does not exist answers nothing, so the parameter really does route the probe into WSL instead of silently falling back to the host. Row 4 shows the guard is live. The stock column also shows why this is wire-safe in the old direction: params: null makes the dispatcher discard request params, so a new client against an old host is exactly today's behavior, never an error.

2. On device. Android release build of this branch installed on a phone, paired to a Windows host running this branch, WSL workspace — see Visual Proof above.

3. Automated.

  • After the rebase onto main (122b8c2): mobile src/tasks src/components src/worktree src/session — 3106 passed; desktop preflight.test.ts + web-preload-api — 16 passed; verify:rpc-params-catalog clean. Mobile Tasks parity: only the hook and statement hashes moved (the local detection send gains a params argument); counts unchanged.

Original runs:

  • pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/rpc/methods/preflight.test.ts — 6 passed (3 new)
  • cd mobile && vitest run src/session/mobile-new-tab-agent-loader.test.ts — 4 passed (2 new)
  • pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/web/preload-api — 11 passed (10 new)
  • Wider sweep, no regressions: src/main/runtime/rpc/** 182 files / 1702 passed; mobile src/session/** 156 files / 1479 passed; src/renderer/src/store/slices + preflight RPC 327 files / 3149 passed
  • pnpm run verify:rpc-params-catalog — clean (this is the gate the regenerated catalog answers)
  • pnpm run tc:node, pnpm run tc:web, mobile tsc --noEmit — clean
  • oxlint, oxfmt --check, check:code-quality:changed, check:max-lines-ratchet — clean, 0 new findings

Platforms actually exercised: Windows host + WSL guest + Android client. macOS and Linux are unaffected — getPreflightWslTarget returns null off win32, so the new field is inert there.

AI Disclosure

Written with Claude Opus 4.5 (via OmO). Every claim above is from a command run in this session; the two screenshots are from the reporter's own phone.

Review

  • Cross-platform: the new field only reaches getPreflightWslTarget, which returns null unless process.platform === 'win32'. macOS and Linux hosts ignore it. No path, shortcut, or shell assumption added.
  • Remote / SSH / local: per docs/reference/remote-wire-compatibility.md Rule 1 this is a new optional field on an existing method, and no reader requires it. New client + old host: the old host declares params: null, so parseRpcRequestParams discards params and answers host-local — the current behavior, not a failure. Old client + new host: absent params parse to {}, every field optional, context undefined, same code path as before. No protocol version bump. The SSH branch (preflight.detectRemoteAgents) is untouched, and its resolution still wins over the new one.
  • Agent / integration neutral: nothing here knows any agent by name; the catalog and the detection command table are unchanged.
  • Performance: one extra string parse per drawer open. When no distro is found the request is byte-identical to before. WSL probing itself is already cached per distro with a TTL.
  • UI quality: no visual change beyond the drawer now having rows to render.
  • Degraded runtime state: a repair-required project runtime cannot be expressed on the wire, and the desktop twin throws instead of probing. The web client therefore sends no request at all and answers with its empty fallback, which is what that user already sees on desktop — never a host-local list that looks launchable.
  • Security: wslDistro crosses a trust boundary from a paired client, so it is validated at the boundary — z.string().trim().min(1).max(255), matching the existing constraint on the same value in accounts.ts. It reaches wsl.exe as an argv element via runWslProcess, never as shell text, and an unknown distro yields an empty result rather than a fallback to another host. A malformed value is rejected before any process spawns.

Agent skill upstream boundary

  • Not applicable — this change touches agent detection plumbing, not the skill installer.

Notes

Adjacent but distinct, and deliberately left alone:

Checklist

  • This PR is small and focused
  • I explained what changed and why (including ELI5)
  • Before/after screenshots attached
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered
  • Targeted tests, typecheck, lint, and the changed-code quality gates pass locally

@coderabbitai

coderabbitai Bot commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

📝 Walkthrough

Walkthrough

Mobile local agent detection now derives an optional WSL distro from the repository or worktree path and passes it with the preflight.detectAgents request. The new-workspace modal and tasks flow provide repository paths, and the new-tab flow extracts the path from the worktree ID. SSH detection continues to use the connection ID. The preflight contract, preload API, and runtime RPC now accept and forward optional WSL detection parameters.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: 🟡 Moderate · up to a1708

Switching repositories can briefly offer an agent unavailable in the newly selected WSL distro. Invalidate the old agent list before merging; the SSH test should also catch unintended local detection calls.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The pull request also changes mobile-new-tab-agent-loader.ts, adds getWorktreePathFromMobileWorktreeId, and adds related New Tab tests. Issue #20060 is limited to the New Worktree modal and the ta… Remove the New Tab loader changes, the related route helper, and their tests from this pull request, or link an issue that explicitly includes the New Tab scope.
Docstring Coverage ⚠️ Warning Docstring coverage is 27.78% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 18 functions across 18 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Issue #20060 requirements are met. NewWorktreeModal passes the selected repository path to useNewWorkspaceExecutionTarget. The tasks flow passes workspaceCreateTargetRepo.path. `getWorkspaceDete…
Description check ✅ Passed The description is complete and directly matches the WSL agent-detection changes. It includes the required ELI5, change details, rationale, linked issues, visual proof, testing results, AI disclosure,…
Title check ✅ Passed The title clearly identifies the primary change: mobile agent detection now runs inside the workspace's WSL distro across New Tab and New Workspace flows.
Full details: Out of Scope Changes check

Explanation

The pull request also changes mobile-new-tab-agent-loader.ts, adds getWorktreePathFromMobileWorktreeId, and adds related New Tab tests. Issue #20060 is limited to the New Worktree modal and the tasks workspace-create flow. The New Tab changes address a separate surface covered by the earlier issue and are not required for this issue.

  • Fix all pre-merge checks with AI

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: cb90ae8a-b889-4bf5-9f01-caebd899da7b

📥 Commits

Reviewing files that changed from the base of the PR and between a0799d8 and 89233b9a90b14418105ae8eaa44e007d3f62e20a.

📒 Files selected for processing (8)
  • mobile/src/components/NewWorktreeModal.test.tsx
  • mobile/src/components/NewWorktreeModal.tsx
  • mobile/src/components/use-new-workspace-execution-target.test.tsx
  • mobile/src/components/use-new-workspace-execution-target.ts
  • mobile/src/tasks/mobile-tasks-refactor-parity.test.ts
  • mobile/src/tasks/use-mobile-tasks-workspace-ssh-state.tsx
  • mobile/src/worktree/workspace-agent-detection-target.test.ts
  • mobile/src/worktree/workspace-agent-detection-target.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

repoPath: '\\\\wsl.localhost\\Ubuntu-24.04\\home\\dev\\project'
})

expect(client.sendRequest).not.toHaveBeenCalledWith('preflight.detectAgents', expect.anything())

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- target file ---'
cat -n mobile/src/components/use-new-workspace-execution-target.test.tsx | sed -n '1,130p'
printf '%s\n' '--- matcher/import context ---'
rg -n -C 3 "sendRequest|detectAgents|detectRemoteAgents|expect\\.anything|vitest|jest" mobile/src/components/use-new-workspace-execution-target.test.tsx mobile/package.json package.json 2>/dev/null || true

Repository: stablyai/orca

Length of output: 11358


🤖 get_repo_knowledge executed:

get_repo_knowledge stablyai/orca /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/architecture /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/learnings /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/conventions

Length of output: 46341


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- hook implementation ---'
cat -n mobile/src/components/use-new-workspace-execution-target.tsx
printf '%s\n' '--- remote detection references ---'
rg -n -C 4 "detectRemoteAgents|detectAgents" mobile src/shared src 2>/dev/null | head -200

Repository: stablyai/orca

Length of output: 272


🏁 Script executed:

#!/bin/bash
set -eu
file="$(fd -t f -i 'use-new-workspace-execution-target' . | head -1)"
if [ -z "$file" ]; then
  printf '%s\n' 'hook file not found'
  exit 2
fi
printf '--- hook implementation: %s ---\n' "$file"
cat -n "$file"
printf '%s\n' '--- remote detection references ---'
rg -n -C 4 "detectRemoteAgents|detectAgents" "$(dirname "$file")" mobile/src 2>/dev/null | head -200

Repository: stablyai/orca

Length of output: 288


🏁 Script executed:

#!/bin/bash
set -eu
file="$(fd -t f -i 'use-new-workspace-execution-target' . | head -1)"
if [ -z "$file" ]; then
  echo 'hook file not found'
  exit 2
fi
printf '%s\n' "--- hook implementation: $file ---"
cat -n "$file"
printf '%s\n' '--- remote detection references ---'
rg -n -C 4 "detectRemoteAgents|detectAgents" "$(dirname "$file")" mobile/src 2>/dev/null | head -200

Repository: stablyai/orca

Length of output: 20704


🌐 Web query:

Vitest toHaveBeenCalledWith expect.anything undefined omitted arguments arity official documentation

💡 Result:

In Vitest, the behavior of toHaveBeenCalledWith regarding undefined arguments and asymmetric matchers is influenced by its Jest-compatible equality implementation, which historically relies on toEqual rather than toStrictEqual [1][2][3]. Key findings regarding your query: 1. expect.anything() behavior: The official Vitest documentation defines expect.anything() as a matcher that matches any value except null or undefined [4]. Therefore, it cannot be used to specifically match undefined arguments in a mock call [5]. 2. Handling undefined arguments: Due to internal implementation details where toHaveBeenCalledWith uses toEqual equality logic, there are known limitations when mixing undefined with asymmetric matchers [1][2]. Specifically, toEqual may skip undefined elements in arrays or properties, causing some asymmetric matchers to fail when used to validate an undefined argument [1]. 3. Arity and omitted arguments: Vitest's toHaveBeenCalledWith is sensitive to the number of arguments (arity). If a function is called with undefined, expect(spy).toHaveBeenCalledWith(undefined) will pass [6]. However, omitting the argument entirely (e.g., expect(spy).toHaveBeenCalledWith()) will not match a call made with undefined [6]. Conversely, if a function is called without arguments, expect(spy).toHaveBeenCalledWith() passes, but expect(spy).toHaveBeenCalledWith(undefined) fails [6]. 4. Recommended approach for robustness: To verify arguments that are undefined or null-adjacent, you should avoid relying on broad matchers like expect.anything(). Instead: - Use literal undefined or null in your assertions when you expect those specific values [6]. - For more complex validation where you need to allow multiple possibilities, consider expect.toBeOneOf([undefined, null]) (if using additional utility libraries) or custom matchers [1][6][5]. - Because toHaveBeenCalledWith uses loose equality, be aware that it might match calls that toStrictEqual would reject (e.g., objects missing a key vs. having a key set to undefined) [3]. If strict argument validation is required, you may need to inspect spy.mock.calls directly and perform your own assertions [7][3]. Official sources confirm that expect.anything() is explicitly documented to exclude undefined and null [4], and issues regarding the interaction between asymmetric matchers and undefined are documented as limitations of the underlying equality algorithm [1][2].

Citations:


Assert the remote detection request and reject every local detection call.

expect.anything() excludes undefined and null, but omitted arguments do not match because toHaveBeenCalledWith checks call arity. Inspect the recorded method names directly, and assert the preflight.detectRemoteAgents call required by the truthy connectionId branch.

Proposed test update
-    expect(client.sendRequest).not.toHaveBeenCalledWith('preflight.detectAgents', expect.anything())
+    expect(
+      client.sendRequest.mock.calls.some(([method]) => method === 'preflight.detectAgents')
+    ).toBe(false)
+    expect(client.sendRequest).toHaveBeenCalledWith('preflight.detectRemoteAgents', {
+      connectionId: 'ssh-1'
+    })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expect(client.sendRequest).not.toHaveBeenCalledWith('preflight.detectAgents', expect.anything())
expect(
client.sendRequest.mock.calls.some(([method]) => method === 'preflight.detectAgents')
).toBe(false)
expect(client.sendRequest).toHaveBeenCalledWith('preflight.detectRemoteAgents', {
connectionId: 'ssh-1'
})

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes

This PR names the WSL distro on the two mobile "create workspace" flows that run before a workspace exists, matching what the New Tab drawer already does (#19886). It is the client half of a two-part fix: safe to land on its own, effective once the host accepts the parameter.

  • getWorkspaceDetectAgentsParams — new helper derives { wslDistro } from a repo path via the shared parseWslUncPath, returning undefined for non-WSL paths so the request stays byte-identical to today's.
  • useNewWorkspaceExecutionTarget — takes an optional repoPath and names the distro on the local branch only; the SSH branch (preflight.detectRemoteAgents) and the connection gate are untouched.
  • NewWorktreeModal — forwards selectedRepo?.path.
  • useMobileTasksWorkspaceSshState — the tasks workspace-create draft forwards workspaceCreateTargetRepo.path.
  • Tests — new cases cover both \\wsl.localhost\… and \\wsl$\… spellings, non-WSL/missing paths, the SSH branch never reaching preflight.detectAgents, and three parity ratchet hashes refreshed (lengths unchanged, so the change is content-only).

I verified the two load-bearing claims in the description. Host-side preflight.detectAgents still declares params: null (src/main/runtime/rpc/methods/preflight.ts), and parseRpcRequestParams returns { value: undefined } for null-schema methods, discarding any incoming params — so sending wslDistro before #19886 lands is inert, never an error. Distro extraction mirrors the desktop's getWslDistroFromPath (src/renderer/src/lib/local-preflight-context.ts), which is the same parseWslUncPath(path)?.distro derivation, and the wslDistro field name is already established on PreflightRuntimeContext / getPreflightWslTarget.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@welldoingk
welldoingk force-pushed the fix/mobile-wsl-agent-detection-create-workspace branch from 89233b9 to 8a06eff Compare September 17, 2026 09:52

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes

This run reviewed the commits added since the prior pullfrog review, which turned a client-only change into a three-layer fix: the host runtime RPC now accepts a WSL detection target, the paired web client forwards its preflight context, and the mobile New Tab loader reads the distro from the workspace's own path.

  • Host runtime RPC accepts a WSL target — preflight.detectAgents and preflight.refreshAgents move from params: null to the new optional PreflightDetectAgents schema (wslDistro trimmed 1..255, wslDefault), mapped by toPreflightRuntimeContext so an unnamed target still means host-local.
  • Paired web client forwards its context — toPreflightDetectAgentsParams flattens the preload PreflightRuntimeContext onto the wire with the host's own precedence (resolved WSL runtime, then explicit distro, then default flag), and both methods skip the probe for a repair-required runtime instead of falling back to the Windows PATH.
  • Mobile New Tab loader names the distro — derives it from the worktree id's UNC path via a new getWorktreePathFromMobileWorktreeId, keeping SSH repos on the remote branch.
  • Wire compatibility verified — old host ignores the new fields (params: null discards them), new host parses missing params as {}; the { commands } callers target the separate relay handler and are unaffected. Recording goldens do not move because the mount adapters never supply a repo path.

ℹ️ PR description predates the host-side change

The description still says "no schema change here" and that the fix "depends on #19886 for effect", but this branch now contains the host-side PreflightDetectAgents schema, the PREFLIGHT_METHODS binding, the regenerated catalog, and the web preload forwarding. A reviewer or merger reasoning about wire safety from the description alone would conclude this is inert until another PR lands, which is no longer true.

Technical details
# PR description no longer matches the diff

## Affected sites
- `src/shared/rpc-contract/preflight-params.ts` — adds `PreflightDetectAgents` (the description claims no schema change).
- `src/main/runtime/rpc/methods/preflight.ts` — binds it to `preflight.detectAgents` / `preflight.refreshAgents` (previously `params: null`).
- `src/renderer/src/web/preload-api/web-host-capability-api.ts` — web client forwarding (the description scopes the change to mobile only).

## Required outcome
- The description reflects that this PR ships both halves: the host runtime-RPC parameter and the mobile/web clients that send it, so merge-order reasoning ("depends on #19886", "inert until the host change ships") is accurate.

## Open questions for the human (optional)
- Is #19886 still expected to land separately with its own host changes, or has its scope been folded into this PR? The title and body both still read as mobile-scoped.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

The Electron IPC handler for agent detection takes a PreflightRuntimeContext, so a
desktop WSL workspace probes inside its distro. The runtime RPC twin that paired
clients call declared `params: null`, so a Windows host always answered from its own
PATH -- where a WSL-only install is absent and the mobile New Tab drawer shows
"No Enabled Agents".

Accept the same optional WSL target on preflight.detectAgents/refreshAgents, and have
the mobile new-tab loader read the distro from the workspace's own UNC path. Hosts that
predate the parameter ignore it and keep answering host-local.
The web twin of the preload preflight API took no arguments, so the runtime
context its callers pass -- `refreshAgents(context)` in the local detected-agent
store and `detectAgents({ wslDistro })` in skill install -- was dropped before the
request left the client. A zero-arg function is assignable to the optional-arg
signature, so this never failed typecheck. Forwarding the raw context would
regress instead: it carries `wslDistro: null` and `projectRuntime`, and the wire
schema rejects null. `toPreflightDetectAgentsParams` maps it with the host's own
`getPreflightWslTarget` precedence.

Also regenerates the RPC params catalog, stale since the schemas moved into
`src/shared/rpc-contract/`, which `verify:rpc-params-catalog` gates.
Flattening a repair-required project runtime to no-target sent the paired web
client to the Windows PATH for a WSL-pinned project, caching launchable-looking
host agents as a successful detect. The desktop twin refuses that probe, so skip
the request and answer with the empty fallback instead.
The New Tab drawer learned to tell the host which distro to probe, but the two
surfaces that pick agents *before* a worktree exists still asked with no target,
so a Windows host answered from its own PATH and a WSL-first user saw an empty
agent list in New Workspace. Both already hold the repo they will create from,
so the distro comes from that repo's path.

Refreshes the Mobile Tasks parity hashes, which cover the tasks source tree.
@welldoingk
welldoingk force-pushed the fix/mobile-wsl-agent-detection-create-workspace branch from 8a06eff to a1708ff Compare September 24, 2026 12:19
@welldoingk welldoingk changed the title fix(mobile): name the WSL distro when creating a workspace fix(mobile): detect agents inside the workspace's WSL distro (New Tab + New Workspace) Sep 24, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: b8ae3048-06ed-4bb3-a3ef-78f874109a84

📥 Commits

Reviewing files that changed from the base of the PR and between 8a06effad9b8418d07089ef1ad7d1196b54906c9 and a1708ff.

⛔ Files ignored due to path filters (1)
  • src/shared/rpc-contract/rpc-params-catalog.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (3)
  • mobile/src/components/use-new-workspace-execution-target.ts
  • mobile/src/tasks/mobile-tasks-refactor-parity.test.ts
  • mobile/src/tasks/use-mobile-tasks-workspace-ssh-state.tsx

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

stale = true
}
}, [client, connectionId, sshGate.status, visible])
}, [client, connectionId, repoPath, sshGate.status, visible])

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Invalidate detected agents when repoPath changes.

If a user switches between two local WSL repositories, the effect requests detection for the new path, but detectedAgentIds continues to expose the previous repository’s agents until that request finishes. The modal can then offer an agent that is unavailable in the selected distro. Include the detection target in the stored state and gate the returned IDs on that target while detection is pending.

@welldoingk welldoingk closed this by deleting the head repository Oct 12, 2026
nwparker added a commit that referenced this pull request Oct 22, 2026
Pass the selected repository to New Workspace agent detection through the host-authoritative runtime resolver, including WSL paths and Windows project preferences. Preserve old-client request shape, SSH routing, and strict ownership/refusal behavior.

This merge credits @welldoingk’s original WSL agent-detection investigation and community PR #20101, adapted to the current host-resolved contract.

Co-authored-by: welldoingk <thkwon96@gmail.com>

Fixes #20060
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant