Repository navigation
[agent-cli] navigate judges attachment on the device it opens - #200
Conversation
2b80c47 to
bd3ba08
Compare
bd3ba08 to
b7deaa9
Compare
b7deaa9 to
b06c81f
Compare
b06c81f to
c501329
Compare
c501329 to
a62ff9e
Compare
a62ff9e to
364b0d2
Compare
364b0d2 to
7de42ed
Compare
7de42ed to
506d819
Compare
2cdfae9 to
298c5e2
Compare
298c5e2 to
db9826c
Compare
db9826c to
6023a0f
Compare
6023a0f to
fab9a14
Compare
| const { appId } = resolveAppId({ | ||
| platform: device.platform, | ||
| appIdOverride: options.appId, | ||
| targetAppIds: [], |
There was a problem hiding this comment.
Recovery always passes targetAppIds: [], so resolveAppId skips every app on the dev server and stops android.package when the project has one.
That is the wrong process when this run opened Expo Go. A stuck Expo Go on this device is not in /json/list, but another Android device's Expo Go on the same server is, and its id is host.exp.exponent. adb -s <this serial> shell am force-stop of that id stops only this device. The old resolveStuckAppIdAsync used that id. This force-stops android.package instead, which exits 0 even when that package is not running, then opens the link again into the same stuck Expo Go.
android.package is normal on a managed app that still runs in Expo Go, so the "else Expo Go" branch almost never runs. With an empty target list the old and new paths already agreed. The regression is this cross-device Expo Go id: it is the same id on every Android device, and it was a correct stop target. Keep ignoring other devices' development-build ids. When resolved.isExpoGo is set, stop EXPO_GO_APP_ID[platform] rather than the config package.
| ` fs.appendFileSync(reversed, JSON.stringify([args[1], Number(args[3].slice(4))]) + '\\n');`, | ||
| ` process.exit(0);`, | ||
| `}`, | ||
| `if (args[2] === 'shell' && args[3] === 'am' && args[4] === 'start' && args.includes('-d')) {`, |
There was a problem hiding this comment.
This accepts am start -d, but the new recovery rows also run pidof and am force-stop. Those still fall through to the unexpected exit 2 below. stopAppOnDeviceAsync treats a non-zero force-stop as failure, so recovered stays false and the second open never happens.
The new tests still pass. They only check that a force-stop argv was recorded, not that stderr says the app was stopped and the link opened again. The unit test that drove a successful force-stop was removed. Accept pidof (exit 1, empty stdout) and am force-stop (exit 0), and assert that recovery sentence on the exit-22 rows.
fab9a14 to
7b1fece
Compare
…routeResolution A pure move, with no change in behavior. resolveRouteUrlAsync, currentTunnelHost, isCallerNamedDevServer and their private helpers move to src/navigate/routeResolution.ts. openRoute.ts imports them and re-exports the three public functions, so no caller changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
navigate validates the route, resolves the device, and then reads the attached apps of that device's scope to decide the app kind, the route URL and the launcher. The same reader serves the launcher wait and the route wait. It builds the scope from the device without probing, reads the device facts once with the first list, and asks the device tools again only for a changed list, within the budget of the wait that polls. So an app that attaches after the accepted Expo Go dialog or the Android recovery counts. When another attached Android device has the bound device's model, it asks adb reverse before the launcher decision and again after this run's own reverse. An app that cannot be placed on the device neither counts as attached nor holds back the launcher. --print-url resolves no device. When nothing attached on Android, the stuck-app recovery stops --app-id, else the app config's app, else Expo Go. This device has no app attached at that point, so an app another device runs on the same dev server is never the one stopped. smoke hands its device choice to openRouteAsync, so the open and the waits after it are about one device. A device lookup that failed is asked again at the route open, and throws its own refusal there. OpenRouteOptions.device is a NavigateDevice; the open extends no lease for a device it is given. Route URL resolution owns its options (RouteUrlOptions) and its result (ResolvedRoute); OpenRouteOptions extends the options, and callers of resolveRouteUrlAsync import it from navigate/routeResolution. The built-command table in navigate-target-selection-test covers local devices, three Android twin rows, cloud fallback with and without a platform flag, launcher ordering, and an empty first list followed only by a local app. cloud-simulator-test counts an app that attaches after the dialog is accepted. navigateAsync-test pins the recovery's app id and one adb device list per unchanged list. The rules are in llp/0005 §Pointing an app at this dev server and §adb reverse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The navigateAsync unit cases for Android become rows and asserts in navigate-target-selection-test: the launcher goes to the main activity before the route link, adb reverse comes first, a run whose app never attaches exits 22 and force-stops only the configured app, and an attached app gets only the route link. Expo Go on Android gets only the route link, in navigate-test. An explicit --ios --eas --app-id row replaces the cloud-simulator case it duplicated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… cases that pin them The force-stop rule, the EAS Expo Go row and the session development-build sentence cite the navigate-target-selection and cloud-simulator e2e cases that replaced their unit tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e classifier now takes it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the worktree is bound to an EAS session (llp/0005 §navigate) resolveRouteUrlAsync now reads the worktree's binding and the caller's --eas, so --print-url, dev --eas and runtime:reload --eas decide Expo Go on the same scope as navigate. An unreadable binding refuses them, like every scoped command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pens the link again and sees it attach (llp/0005 §navigate) The stub adb now answers shell pidof and shell am force-stop, so the recovery's success path runs in e2e: attachRecovered is true and the app counts as attached. The rows where no app ever attaches now see the second open of the route link. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… no env var for it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…attaches during the attach wait (llp/0035 §Readers) The wait asks adb reverse again when the new list shows twins, so the twin does not leave the own app undetermined until the attach timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…run opened Expo Go (llp/0005 §navigate) The stub adb already accepts pidof and am force-stop; the exit-22 rows now assert the recovery sentence in stderr. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tools on AGENT_CLI_NO_DEVICE=1 now skips the EAS binding unless the run passes --eas, so the case sets it to 0 like the other bound-device cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd the wait-retry test passes its deadline per read Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n through adb reverse, as navigate does (llp/0005 §navigate) The URL resolver passed no dev server URL, so the reverse question was never asked and the Expo Go app on the bound emulator stayed undetermined. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e comes before the route link Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g (llp/0005 §navigate) When pidof sees no app, the attach recovery still opens the link again, but attachRecovered stays false and the failure text says nothing was stopped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…binding when the dev server lists no app (llp/0005 §navigate) The scope is read only once the dev server lists an app, so a corrupt binding file no longer refuses a URL that does not depend on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… macOS only, where a local iOS binding is read Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every runtime command acts on the device bound to this worktree, and nothing else attached to the same dev server, and says which device it acted on.
This PR makes
navigatedecide "attached" from the device it opens.Stack: #202 → #203 → #204 → #206 → #217 → #218 → #207 → #200 (this is 8/8).
Problem
navigatecounted the attached apps of every platform. N3-25 (also DF22): afterdev --android --eas, with only a localiPhone 17 Proapp on port 8081,navigate "/street/…"with no flag counted the iOS app as attached. It skipped the launcher, opened only the route link, and exited 22 after 120 s. On Android, the stuck-app recovery could also stop an app that another device ran on the same dev server.Fix
navigatevalidates the route, resolves the device, and then reads the attached apps of that device's scope to decide the app kind, the route URL and the launcher. One reader serves the launcher wait and the route wait. It reads the device facts again only for a changed list, within the budget of the wait that polls. So a cloud app that attaches after the accepted Expo Go dialog counts. When an Android twin of the bound device is attached,navigatereadsadb reversebefore the launcher decision and again after this run's ownadb reverse. When nothing attached on Android, the recovery stops--app-id, else the app config's app, else Expo Go. smoke hands its device to the route open, so the open and the waits are about one device.--print-urlreads the scope asnavigatedoes,adb reverseincluded, once the dev server lists an app. The recovery stops Expo Go when this run opened it, and says the app was stopped only whenpidofsaw it running.The first commit is a pure move of route URL resolution to
src/navigate/routeResolution.ts. The second commit holds the behavior.Red tests: navigate-target-selection-test "no flag, bound Android emulator, only an iOS app and another model's Android app attached"; navigateAsync-test "asks adb for the device list once while the attach wait polls an unchanged list"; e2e cloud-simulator-test "counts the app that attaches after the dialog is accepted"; navigate-target-selection-test adds three Android twin rows, one of them "no flag, bound Android emulator beside a twin, neither reverses the port before this run".
Live check: the
live-cloudiOS suite passed 17 of 17 at the stack top on an EAS session:navigate / --easexit 0, attached after 3 ms. Plain main gives the same results.Watch: after a wait's budget ends, its later polls keep the last device facts.
🤖 Generated with Claude Code