# JMobile integrations

The public Android control service is `https://jmobile.strogleapp.com`. Normal dashboard/app users sign in. Automation uses a personal key from **API access**. Every SDK/API/MCP request remains scoped to that key's account. Android v0.3.0 is required for app inventory, rich observations, focused keyboard actions and PNG capture.

## Python SDK

```bash
python -m pip install https://jmobile.strogleapp.com/downloads/jmobile_sdk-0.7.4-py3-none-any.whl
export JMOBILE_API_KEY='YOUR_PERSONAL_KEY'
```

```python
from jmobile import JMobile

client = JMobile()
print(client.devices())
phone = client.device("YOUR_DEVICE_ID")
with phone.lease(seconds=120) as controlled:
    print(controlled.apps())
    controlled.open_app("com.android.settings")
    state = controlled.observe()
    print(state["phone_state"], state["nodes"])
    controlled.screenshot("screen.png", format="png")
```

Use `tap(text=...)`, `tap(resource_id=...)`, `tap(node_id=..., revision=...)` or native `x,y`; `set_text(value, resource_id=...)`; `keyboard("exact user text", clear=True)`; `keyboard(key="enter")`; `swipe(x,y,end_x,end_y)`; and `navigate("home"|"back"|"recents")`. Exact selectors with multiple matches require an index. Observe again after acting. SDK package source is `sdk/python/`; `AsyncJMobile` supplies async device listing/commands/tasks through worker threads. Async cancellation does not reverse a command already sent to Android.

Lease contexts return a separate lease-bound Device. Use that object for every command during the lease. If a lease lasts longer than its initial reservation, call `reservation.renew()` before expiry. Lease TTL is 30–600 seconds. All native commands by other writers are blocked; connection metadata remains readable through device listing/details. Existing ADB streams block lease acquisition; leased devices block new ADB streams, even if a lease token is supplied, so two command paths cannot mutate the same phone concurrently. Expired/released lease tokens cannot execute actions. A server restart ends leases and interrupts tasks; it never replays actions.

## Remote MCP

MCP Streamable HTTP endpoint:

```text
https://jmobile.strogleapp.com/mcp
Authorization: Bearer YOUR_PERSONAL_KEY
```

Configure it in an MCP client that supports remote HTTP servers with custom bearer headers. Example configuration shape (adapt field names to your client):

```json
{
  "mcpServers": {
    "jmobile": {
      "type": "http",
      "url": "https://jmobile.strogleapp.com/mcp",
      "headers": {"Authorization": "Bearer YOUR_PERSONAL_KEY"}
    }
  }
}
```

This endpoint uses personal bearer keys, not OAuth login/discovery. A client requiring OAuth-only connectors needs an additional OAuth integration. Never paste private server credentials into a shared config.

Core tools (expanded tool groups are documented below): `mobile_list_devices`, `mobile_get_device`, `mobile_status`, `mobile_read_screen`, `mobile_list_apps`, `mobile_screenshot` (returns an MCP image), `mobile_tap`, `mobile_set_text`, `mobile_keyboard`, `mobile_swipe`, `mobile_open_app`, `mobile_navigate`, `mobile_acquire_lease`, `mobile_renew_lease`, `mobile_release_lease`, `mobile_create_task`, `mobile_list_tasks`, `mobile_get_task`, `mobile_cancel_task`. Resource: `jmobile://devices`.

For complete natural-language phone goals use `mobile_run_agent`: JMobile runs Jev/TypeSafe on the server. The provider key belongs to the operator; users supply only their personal JMobile key. The low-level tools still support host-managed observe/act/verify loops and deterministic tasks. Tool errors stop the operation; an uncertain mutation is never retried automatically.

## Local stdio MCP

Clone/access the JMobile repository, install Node 22+ dependencies with `npm ci`, then configure:

```json
{
  "mcpServers": {
    "jmobile": {
      "command": "node",
      "args": ["/absolute/path/to/jmobile/server/mcp-stdio.mjs"],
      "env": {
        "JMOBILE_URL": "https://jmobile.strogleapp.com",
        "JMOBILE_API_KEY": "YOUR_PERSONAL_KEY"
      }
    }
  }
}
```

Or run `npm run mcp` with these variables already exported. The process writes only MCP messages to stdout. It calls JMobile's authenticated public API rather than accessing private Flask internals or local USB.

## mobile-jev device compatibility

The control API subset at `https://jmobile.strogleapp.com/v1` supports the `MobilerunDevice` client reviewed at upstream revision `395fc222beac4f059f9a0beb337d114a2b066e99`. It is not a blanket promise that every MobileRun SDK route is implemented.

```text
MOBILERUN_BASE_URL=https://jmobile.strogleapp.com/v1
MOBILERUN_API_KEY=YOUR_JMOBILE_PERSONAL_KEY
MOBILERUN_DEVICE_ID=YOUR_JMOBILE_DEVICE_ID
MOBILERUN_TEXT_COMPLETION_MODE=committed
TYPESAFE_API_KEY=YOUR_TYPESAFE_KEY
```

Start with `pnpm devices`, `pnpm doctor`, `pnpm agent observe`, and a benign bounded CLI goal. TypeSafe/model usage is separate. This repository's actual upstream device client is exercised against the JMobile protocol in integration tests using a simulated phone. A real model-driven task on the physical updated phone still needs verification and a TypeSafe credential.

Supported routes relative to `/v1`:

| Method | Route | Behavior |
| --- | --- | --- |
| GET | `/devices?page=1&pageSize=100` | Scoped paginated device list |
| GET | `/devices/{id}` | Readiness including accessibility/lock state |
| GET | `/devices/{id}/apps` | Launchable apps visible to the Android companion |
| PUT | `/devices/{id}/apps/{package}` | Open a launchable installed app |
| GET | `/devices/{id}/ui-state?filter=false` | Nested accessibility state and native screen geometry |
| GET | `/devices/{id}/screenshot` | PNG bytes |
| POST | `/devices/{id}/tap` | Native x/y |
| POST | `/devices/{id}/swipe` | startX/startY/endX/endY/duration |
| POST | `/devices/{id}/global` | 1 Back, 2 Home, 3 Recents |
| POST | `/devices/{id}/keyboard` | text, clear |
| DELETE | `/devices/{id}/keyboard` | Clear focused input |
| PUT | `/devices/{id}/keyboard` | Keys 4, 61, 66, 67, 112 |

Keyboard support uses accessibility; apps may reject focus/IME actions. Password-focused keyboard mutation is explicitly rejected. Android app visibility limits the app list to launchable apps; it is not an unrestricted package-manager dump. PNGs have a maximum 4096-pixel longest edge; screenshots do not redefine native tap coordinates. Unknown app versions and old APKs do not become capable merely because the server accepts a method name.

Jev's standard studio live-view component still depends on MobileRun's streaming protocol and credentials. It must be replaced/adapted; our compatibility routes do not implement that video service. The reviewed upstream client does not use our task-lease header automatically. For exclusive runs, use an adapter/proxy that acquires/renews a JMobile lease and supplies `X-JMobile-Lease` on each control request. The unmodified client's standalone commands work without taking a lease, retaining per-command serialization only.

## Bounded asynchronous tasks

These are deterministic plans, not natural-language planning. Run existing commands, wait up to 10 seconds per step, and assert exact text/resource IDs and optional checked state. Maximum 50 steps, 600 seconds, five active tasks per account and twenty globally. Only one task may lease a phone. Creation returns immediately; execution outlives the requesting HTTP connection.

```python
task = client.create_task("YOUR_DEVICE_ID", [
    {"method": "openApp", "params": {"packageName": "com.android.settings"}},
    {"method": "assert", "params": {"text": "Settings"}}
], timeout_seconds=60)
for event in client.task_events(task["id"]):
    print(event["status"], event["completed"])
```

Endpoints: `POST/GET /api/tasks`, `GET /api/tasks/{id}`, `POST /api/tasks/{id}/cancel`, `GET /api/tasks/{id}/events` (SSE). Closing an attached events stream does not cancel the task. Cancellation stops before the next action and cannot undo an action already dispatched. Deadline checks bound further dispatch; an in-flight native action can finish after the deadline. History stores safe step metadata, not input text, screenshots, or credentials. A failed assertion stops the run. Restart recovery marks unfinished records `interrupted` with `server_restarted_outcome_unknown`, preserving existing events without replay.

## Capabilities and limits

`GET /api/capabilities` describes server methods and the baseline Android version (v0.3.0); expanded controls require v0.4.0. It is not proof that a particular phone has granted permissions. `GET /api/devices/{id}` and `status` report its actual readiness.

See `docs/CAPABILITY_AUDIT.md` for the entire research comparison, including services not implemented. The API/SDK/MCP do not bypass PIN locks, force-stop, power loss, protected content, or Android permission revocation. ADB remains optional and separate from accessibility operations.


## Expanded device controls (v0.4.0)

Install Android **v0.4.0** for `mobile_capabilities`, `mobile_device_info`, `mobile_open_link`, `mobile_node_action`, `mobile_gesture`, `mobile_clipboard` and `mobile_overlay`. Native-coordinate strokes support long press, double tap, drag and simultaneous two-finger pinch. `mobile_navigate` also offers notifications, quick settings, power dialog, split screen, lock screen and supported Android accessibility/global actions. Not every app implements every node action. Overlay labels represent a snapshot, so reread UI after changes.

The v0.4 release introduced 36 MCP tools. ADB additions are `mobile_adb_status`, `mobile_adb_pair`, `mobile_shell`, `mobile_list_packages`, `mobile_manage_app`, `mobile_files`, `mobile_settings`, `mobile_reboot`, `mobile_record_video`, `mobile_browser`. They share the same per-account authorization, renewable leases and single-writer fences as native controls. There is no hidden retry after failure. Shell commands may change phone data and run only with Android's shell UID privileges. Nonzero process exits fail rather than completing a deterministic task successfully.

To use ADB, enable Developer options/debugging on your own phone, configure the actual current connect/pair ports under the app's advanced settings, and enable its ADB relay. Wireless debugging requires owner-supplied pairing code (`phone.adb_pair(code)`) and remains subject to Android/OEM restrictions; classic TCP debugging may require accepting this server's RSA key on the phone. `phone.adb_status()` verifies authorization; a configured port/green relay toggle does not. Keys persist per phone across server restarts and remain private. The deployment uses a project-bundled platform-tools binary, private loopback ADB daemon per operation, network-only target and no host USB discovery. ADB is optional; accessibility controls continue independently.

REST invocation for every new operation is `POST /api/devices/{id}/command` with `{"method":"files","params":{"action":"list","path":"/sdcard/Download"}}`. Native methods are `capabilities`, `deviceInfo`, `openLink`, `nodeAction`, `gesture`, `clipboard`, `overlay`. Privileged methods are `adbStatus`, `adbPair`, `shell`, `packages`, `appManage`, `files`, `settings`, `reboot`, `recordVideo`, `browser`. Tool schemas describe their exact parameters. `GET /api/devices/{id}/capabilities` also reads native capabilities. The `/v1` compatibility subset adds clipboard, time, capabilities, deep links and packages; this still is not drop-in vendor-wide SDK compatibility.

```python
from jmobile import JMobile
phone = JMobile().device("YOUR_DEVICE_ID")
print(phone.capabilities())
with phone.lease(120) as controlled:
    controlled.open_link("https://example.com")
    # Observe, select, act, verify; real coordinates come from that observation.
    print(controlled.observe())
    # Optional authorized ADB operations:
    print(controlled.adb_status())
    print(controlled.packages(third_party=True))
    controlled.push_file("test.apk", "/data/local/tmp/test.apk")
    controlled.manage_app("install", path="/data/local/tmp/test.apk", replace=True)
    controlled.record_video("/sdcard/Download/jmobile-test.mp4", seconds=5)
    controlled.pull_file("/sdcard/Download/jmobile-test.mp4", "test.mp4")
```

Files use <=32768-byte base64 chunks; SDK streams chunks without automatic retry/resume. Use a lease to prevent another controller changing a file between chunks. Offset zero truncates on write. Symlinks/file permissions are those of the device's shell UID; this is not an isolated device filesystem sandbox. Capture files remain on the phone until explicitly deleted. Video has no audio and secure surfaces remain protected. CDP requires an exposed `chrome_devtools_remote` socket and an explicitly selected observed page; other browsers/WebViews may not expose it. Reboot intentionally interrupts access. Global locale/timezone/GPS/proxy/identity capabilities must not be inferred from generic shell availability.

The [complete 85-method MobileRun comparison](jmobile-mobilerun-function-matrix.md) records remaining provider, system privilege and infrastructure gaps. Full device-cloud, stream/media injection, carrier/eSIM, saved screenshot/recording catalogs, hosted planning and workflow products remain separate work.


## Physical Android test tools — API/SDK v0.5.0, APK v0.5.1

The v0.5 release introduced 41 tools; current v0.7 has 44. New methods are `mobile_setup_status`, `mobile_location`, `mobile_esim`, `mobile_open_media`, `mobile_open_device_settings`. Equivalent sync/async Python methods: `setup_status()`, `location(...)`, `esim(action='status')`, `open_media(action='photo')`, `open_device_settings(action)`. REST command methods are `setup`, `location`, `esim`, `media`, `deviceSettings`. APKs older than v0.5.0 return `app_update_required` before dispatch.

In the Android app, open **Test tools → Enable location testing**, grant location/notification permissions and enable Android Location. Testing has a separate visible foreground notification and Stop action. Starting it is a local app action, never a remote command or automatic boot/crash resume. For mock coordinates, select **Developer options → Select mock location app → JMobile**. Approximate permission remains approximate for real readings; response fields include `ageMs`, `accuracy` and `mock`. No fix returns `location_not_available_yet`; old fixes are labelled with their age, not silently treated as current. No coordinates are persisted in task history or app settings.

```python
phone = client.device('YOUR_DEVICE_ID')
print(phone.setup_status())
print(phone.location('status'))
# After local enablement and mock-app selection:
phone.location('set', latitude=51.5, longitude=-0.1, seconds=60)
try:
    print(phone.location('get'))
finally:
    phone.location('reset')
phone.open_device_settings('date_time')  # Change through observed Android UI.
phone.open_media('photo')               # Launch only; observe before taking a photo.
print(phone.esim())                     # Hardware/enabled state, no SIM identifiers.
```

Mock duration is 1–1800 seconds (default300), accuracy 1–10000 meters, finite latitude/longitude within physical bounds. Set/reset uses Android test providers and cleans only JMobile's tracked providers. A foreground timer and best-effort OS alarm perform expiry cleanup; sleeping/force-stopped phones can delay alarms. Pending provider ownership is persisted before mutation, without coordinates, so reopening JMobile or starting testing cleans up an interrupted session. If Android revokes mock-app privileges before cleanup, status reports `cleanupPending`; select JMobile again and reset. Disconnect/logout/notification Stop attempt cleanup; explicit reset in a finally block is recommended. No old mock is replayed after restart.

`mobile_esim` supports `status` and `manage`: Android user-confirmed management UI, no silent download/activation, carrier privileges, profile inventory or EID/IMSI readout. `mobile_open_media` supports `photo`, `video`, `audio` and launches the real camera/recorder app; capture/recording still needs accessible UI actions and that app's permissions. It is not virtual media injection or a server media stream. `mobile_open_device_settings` supports `wifi`, `bluetooth`, `date_time`, `language`, `location`, `display`, `developer`, `accessibility`, `battery`, `app`; verify resulting changes through UI/device info. New UI-launch actions require enabled accessibility and an unlocked screen.

The reviewed `/v1/devices/{id}/location` subset supports GET read, POST test coordinates and DELETE reset, preserving per-user ownership and lease headers. Owner opt-in/mock selection requirements still apply. See [Android setup requirements](jmobile-android-requirements.md) for the exact distinction between supported physical-phone features and privileged injection/carrier functionality.

## Hosted Jev runner — API/SDK v0.7.1, 44 MCP tools

Users connect the same authenticated `/mcp` endpoint. They do **not** supply a TypeSafe key. `mobile_agent_status` returns configured/model/bounds without secrets; `mobile_run_agent` accepts `deviceId`, `goal`, optional exact `texts`, `maxSteps` (1–50, default20), `timeoutSeconds` (1–600, default120), `confidence` (0–1, default0: cutoff disabled, matching upstream) and optional `successCriteria` (up to10 exact text/resourceId/checked selectors).

The tool immediately returns a task ID. Poll `mobile_get_task`, stream existing task events via REST/SDK, or use `mobile_cancel_task`. The runner acquires/renews its own device lease: **do not acquire a separate lease before starting it**. One agent task per account and two globally; existing deterministic task/account bounds also apply. Loss of account access, cancellation, server shutdown or deadline prevents further dispatch. A command already sent cannot be undone, and an uncertain mutation is never repeated automatically. Server restart marks unfinished tasks interrupted; it never replays them.

The server uses pinned MIT `droidrun/mobile-jev` policy, observation validation and observe/act/verify loop. Its typed actions cover installed app launch, observed taps, focused exact text, directional scrolling, navigation, waits and completion/blocking decisions. It does not invent arbitrary prose, unlock the phone or choose arbitrary shell/file commands. Expanded low-level JMobile tools remain independently available. Goals, supplied text and accessibility context are sent to TypeSafe; no screenshot is sent by this text/tree policy. Run inputs/screens/provider credentials are not persisted in task history; history contains safe operation/status/count metadata.

A final `jev_result` event distinguishes `verification: model_assessed` from `assertions_passed`. A `completed` task without explicit success criteria means Jev reported DONE, not independent proof that the user's entire intent is satisfied. Inspect the resulting phone state. Explicit assertions reread the final accessibility tree and must all match. Non-DONE exits (blocked, uncertain, needs_input, step_limit, stuck, unstable_screen) fail with a corresponding `jev_...` code.

```json
{"deviceId":"YOUR_DEVICE_ID","goal":"Open Settings","maxSteps":10,"timeoutSeconds":60}
```

REST: `GET /api/agent`, `POST /api/agent/tasks`; status/cancel/events use the existing `/api/tasks/{id}` routes. Python sync/async: `client.agent_status()`, `client.run_agent(device_id, goal, max_steps=10, timeout_seconds=60, texts=[...], success_criteria=[...])`, followed by `task()`, `task_events()` or `cancel_task()`.

Operator configuration is private `.local/jev-config.json` with `apiKey` and optional `model` (default `jev-latest`). Use protected secret provisioning; never put the key in public config, browser, APK, logs or client MCP configuration. The service reads it via systemd LoadCredential; the installer initializes an empty, disabled configuration only when absent. Restart the gateway after rotating the key. `server/vendor/mobile-jev/UPSTREAM.md` records revision/license. Current provider contract: [TypeSafe API](https://docs.typesafe.ai/api).

## Faster Jev execution (0.7.1)

Submit a complete bounded goal with `mobile_run_agent`, then call `mobile_wait_task` with its task ID and `timeoutSeconds: 20` (maximum 25). A wait timeout leaves the original task running; wait again rather than submitting the goal again. Optional `afterEventCount` returns when new progress appears. Ownership and authorization are checked while waiting. Python clients expose `wait_task` in both sync and async clients.

APK0.6.3 adds an atomic action/observation command: the phone checks the expected UI, performs one action, settles locally, and returns the new screen tree in one request. Older phones retain the existing execution path. Install the update to enable this optimization. Unknown action outcomes are never retried automatically. One uncertain model decision may trigger a read-only refresh; an explicitly selected confidence cutoff is respected.

Task events include provider, device, authorization, persistence, total wall time and atomic-step counts. Component times can overlap and should not be added together. These diagnostics identify actual bottlenecks; reduced request counts do not imply a measured physical-phone speedup.


## Dashboard device chat

The signed-in dashboard exposes the hosted runner directly under each phone's controls. Browser operations use same-origin WSS (`agent.status/start/lookup/list/get/cancel`) through Node; only Node calls Flask. No personal API key or provider credential is sent to the browser. A private `session.authorize` RPC derives the current owner/device scope from the dashboard session. Task authorization stays bound to the original login, so logout/revocation prevents subsequent dispatch even if the WebSocket later signs in again.

Chat defaults:50 steps,600 seconds, server confidence default. Messages and drafts are per-device/tab memory, cleared at logout; persisted task history remains metadata-only. Refresh/reconnect restores active task metadata. Starts use a scoped request ID and a bounded15-minute in-memory deduplication cache. A lost response is recovered by lookup, never by resubmitting the goal. Missing lookup after restart/expiry reports an unknown start outcome and checks existing history; it never silently replays. Stop cannot undo already executed actions. Final text explicitly distinguishes model-reported completion from passed assertions.


Text action progress distinguishes command acceptance from exact field verification. New task events include `jev_input` with `verified`/`unverified` and a safe reason (`exact_value`, `value_mismatch`, `field_missing`, `field_ambiguous`, `context_changed`); no entered text is stored. Browser input matching tolerates changed tree positions when the unique focused field retains an independent hint or bounds anchor. A mismatch remains a stop: Jev does not submit, retype, or replay the uncertain action. The dashboard shows unconfirmed text without a green check and displays Stopped for a failed test; Phone ready describes an idle phone without a previous failed task.


Upstream alignment (0.7.1): default confidence cutoff is0 in REST, MCP, Python SDK and dashboard, matching the original Jev CLI/studio. Explicit confidence cutoffs still apply to both the selected operation and its selected target; safe task events record both scores and the threshold. A valid probability distribution, supported operation, observed target, fresh state, lease, authorization and exact supplied text remain required. This does not turn model completion into independently verified success.

Standalone dashboard requests such as `capture the screen` or `take a screenshot` call the existing authenticated screenshot command directly, without TypeSafe or a Jev task. A shared browser/server classifier recognizes standalone English/German capture requests, including “make a screenshot of phone”. Compound or unsupported capture requests stop before Jev actions, with guidance to capture separately. The server rejects capture goals submitted through agent-task endpoints, protecting cached dashboard clients too. Captures are displayed in chat, never persisted or sent to TypeSafe, and only the newest capture is retained in tab memory. They can be requested while locked, subject to Android screenshot restrictions. A timed-out capture is never automatically repeated. MCP users should call `mobile_screenshot` directly.


Connection health (Android0.6.4): heartbeat-v2 is negotiated per native socket; legacy apps remain supported. The phone reports Connected only after a welcome and application-level ack, with sequence-based freshness. Stale modern sessions are rejected for dispatch and proactively terminated independently of WebSocket pong. Browser WSS uses its own lightweight connection.ping and watchdog; reconnect restores session/task metadata, never blindly resends a command or agent start. No API/MCP/SDK signature change; installed Android app0.6.4 is needed for native watchdog fixes.

Accessibility readiness in native0.6.5: status/setup and heartbeat metadata add accessibilityEnabled, accessibilityBound and accessibilityState (bound/waiting_for_system/disabled). Existing accessibility still means an active service reference. A quiet or locked screen does not imply a disconnected service. Android manages rebind and permission; there is no silent accessibility re-enable action. API/MCP/SDK signatures unchanged.

Native0.6.6 keyboard text with clear=false inserts at the observed cursor/replaces selection; it rejects an unknown nonempty-field selection instead of guessing append. clear=true still replaces the entire field. App inventory runs off-main with native12s bounded completion (app_inventory_timeout); timeout does not replay commands or deliberately disconnect the transport. Optional accessibilityEnabledKnown=false/accessibilityState=unknown indicate Android denied diagnostic settings access, rather than proving a permission is disabled.

Native0.6.7: app_inventory_busy means the previous Android package query has not returned despite cancellation. There is no queued inventory replay; status/heartbeats and relay readers use independent execution paths.

Native0.6.8: accessibility_service_disconnected means the OS setting is enabled but no live service is bound. Keep the server connection enabled and use owner-visible Repair phone controls in the app. control_failed_outcome_unknown must not be blindly retried. mobile_setup_status.processHealth caches the last own main-process exit (Android11+) with reasonCode/reason/timestamp/status only; this is diagnostic evidence, not proof of an accessibility failure cause.

Native0.6.9 revisions are opaque JavaScript-safe integer tokens, unique across service bindings in the same process. Do not infer event counts; read a new UI after stale_observation. Saving ADB settings no longer disconnects the main transport; only obsolete ADB relays close. Known ADB transport failures can now report adb_port_unreachable, adb_connect_timeout or adb_socket_permission_denied instead of a generic authorization error. File writes use bidirectional non-PTY shell stdin, still with framed exit-status checks/no replay. Task cancellation/deadline is rechecked after async authorization/persistence before any new dispatch.

Native0.6.10: mobile_setup_status.adbPorts reports automatic/state/connectPort/pairingPort; missing ports are null. Automatic mode discovers this phone’s own _adb-tls-connect/_adb-tls-pairing announcements; connection and pairing ports are distinct. adb_port_not_discovered means wait for discovery/Find ports now, not that the ADB key is authorized. No prior operation is automatically retried. Manual legacy ports remain available when automatic mode is disabled.

## SDK0.7.2 and system audit (2026-10-27)

API remains0.7.1; native release is0.6.12/code22. SDK `pull_file` writes bounded chunks to a private temporary file beside the destination, replaces the destination only after all reads succeed, and removes incomplete temporary data on failure. Existing local content is preserved on a remote/transport/data failure; device actions are never silently retried. Use a lease if the remote source must stay consistent against other controllers. Concurrent unrelated writers or remote file changes are not a snapshot guarantee. See [system audit](jmobile-system-audit-2026-10-27.md).

## SDK0.7.3 and audit round two

Malformed HTTP error codes, truncated bodies and non-object API replies raise structured SDK errors, retaining uncertain mutation outcomes without automatic retries. Malformed SSE events and screenshot payloads also use SDK errors. Task event streams now respect backpressure and are limited to8 per account/128 globally; long-poll wait limits are unchanged. Server shutdown waits for admitted task creation/persistence and closes event streams. See [second system audit](jmobile-system-audit-2026-10-27-round2.md).

## Security/privacy release (2026-10-27)

API0.7.1/SDK0.7.3/MCP44 signatures stay unchanged. Android0.6.15/code25 disables debug access and prevents password-length inference through select_text. Delayed MCP operations and raw-socket admission obey current revocation; user interfaces purge old-account views and revalidate history restores. CLI command requests refuse redirects. See [security findings and limits](jmobile-security-privacy-audit-2026-10-27.md).
