Skip to content

appduct link --relaunch for simulators and emulators, and --replace so a re-paired app keeps its alias #68

Description

@V3RON

Why

The scenarios an agent most wants to test on its own start from a cold process: onboarding, a push notification or deep link opening the app from scratch, a data migration, "does the cart survive a kill". By design, process death ends the session (resume tokens never touch disk, docs/ARCHITECTURE.md §1, §3, §6), and this issue does not propose changing that.

appduct link --open <target> already does most of a restart: delivering a link to a running app supersedes the session it holds (AppductClient+Session.swift's connect(input, supersede: holdsSession) and its Kotlin/JS counterparts), and delivering to a dead app launches it (simctl openurl, am start). What it does not do:

  1. Kill the process first. --relaunch exists only for the experimental ios-device target, as devicectl --terminate-existing. On ios-sim and android a delivered link is a warm deep link into the running app, never a cold start.
  2. Keep the alias. When the app closes its old socket (superseded) or dies (killed), the daemon suspends the old session for graceSeconds (600 s by default) rather than ending it. The new claim from the same device model therefore slugs to <model>-2, the session id changes, and with several devices live every namespaced MCP tool name shifts. An agent that scripted pixel-8__seed_cart is now calling a tool that no longer exists.
  3. Do it from MCP. appduct_connect has relaunch for ios-device only, and no way to end the superseded session.

Proposal

Two flags on the existing link command and the existing appduct_connect tool. No new command, no new RPC method, no daemon-side memory of where a link was delivered.

--relaunch for every --open target. Terminate before delivering:

target terminate then
ios-sim xcrun simctl terminate <udid> <bundleId> existing simctl openurl
android adb -s <serial> shell am force-stop <appId> existing am start
ios-device --terminate-existing (unchanged) unchanged

Both new paths need the app id, which is what #64 makes explicit for android and ios-device; ios-sim takes the same --app-id/--bundle-id value, and iosBundleId in config.json covers it as it does for ios-device. "Terminate" of an app that is not running is a no-op, not an error, so the flag is safe to pass unconditionally in a script.

--replace [selector]. Revoke the named session (or, with no selector, the single live session; ambiguous_session if several) immediately before delivering, so its alias is freed and the new claim reuses it. sessions.revoke already exists; this only sequences it. Without --replace, today's behaviour is unchanged: the old session stays suspended for the grace window.

MCP. appduct_connect gains relaunch for all targets and replace?: string | true, mapped to the same code. The result already reports sessionId; add replacedSessionId when replace was used, and alias, so the agent can see that the names it knew are still valid.

Optional and Android only: --clear-data runs adb shell pm clear <appId> between terminate and deliver, for a true first-launch test. iOS simulators have no cheap equivalent short of uninstall, so leave it out rather than fake it.

Where it lives

  • Terminate commands next to the launch commands in packages/appduct/src/cli/open-target.ts, behind the existing injectable ExecFn seam, so they are unit-tested without a device.
  • --relaunch is already parsed for link; widen its target check and thread it into deliverToOpenTarget. --replace goes through packages/appduct/src/commands/link.ts (one sessions.revoke call before link.create), the route in cli/routes/link.ts, and the cli.command(...) spec in create-cli.ts.
  • packages/appduct/src/mcp/connect-tool.ts for the MCP side, sharing the same helpers.
  • Skill: replace the implicit "the session survives reloads" with a short "Restart the app" note in the CLI and MCP flows: appduct link --open ios-sim --relaunch --replace, or appduct_connect({ relaunch: true, replace: true }).

Acceptance criteria

  • appduct link --open ios-sim --relaunch --replace against a running app yields a new active session with the same alias in one command; same for --open android.
  • appduct_connect({ relaunch: true, replace: true }) followed by appduct_wait_for_session does the same over MCP, and notifications/tools/list_changed fires with the proxied names unchanged in a single-device setup.
  • --relaunch without an app id on ios-sim/android is a usage error naming the flag and the config key, before anything is minted or terminated.
  • --replace with several live sessions and no selector fails with ambiguous_session listing the aliases, before anything is minted or terminated.
  • Terminate commands covered through ExecFn for all three targets, including the "app not running" no-op; an integration test with a fake app covers revoke-then-claim alias stability.
  • appduct README command table, docs/ARCHITECTURE.md §8, skills/appduct/SKILL.md and CHANGELOG.md updated.

Out of scope

  • Persisting resume tokens so a session survives process death.
  • Automatic detection of which device a session belongs to. The agent re-passes (or lets appduct_connect auto-detect) the target exactly as it did the first time.
  • Clearing app data on iOS simulators.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions