diff --git a/README.md b/README.md index 1228a1c..39f6967 100644 --- a/README.md +++ b/README.md @@ -240,7 +240,7 @@ type ISessionManager = { Core captures `sessionManager.getPlatformDriver?.()` before calling the consumer's `cleanup()`, then calls the driver's optional `dispose()` hook. Disposal runs even when consumer cleanup fails. This applies to `mm cleanup`, forced relaunch, failed launch, daemon shutdown, and server startup rollback. A failed disposal fails that cleanup call and is retained for a later attempt. The daemon owns that retention (one per `createServer` instance), so a later `mm cleanup`, forced relaunch, or daemon stop retries it, whatever wrapper of the session manager a tool receives. Only the current session's cleanup and disposal can fail a call. Drivers retained from earlier sessions are retried best-effort and in parallel. When they fail again, the daemon writes a `[WARN]` line to its log, and the current cleanup or launch continues. Tools reach this retention through `ToolContext.cleanupSession()`; without it (a context built outside the daemon), the tools fall back to retention per session-manager object. -`MobilePlatformDriver.dispose()` delegates to its backend's optional `dispose()` hook. With a device-mcp version that implements private IDB companions, this stops only that backend's companion and recording. Older device-mcp versions remain compatible, but do not gain companion teardown. Adopt the device-mcp lifecycle release in the consumer after it is published; this change does not update the core dependency range. +`MobilePlatformDriver.dispose()` delegates to its backend's optional `dispose()` hook. Core requires `@metamask/device-mcp` 0.7.0 or later. On iOS, the device-mcp `IdbBackend` uses a private IDB companion per backend, and disposal stops only that backend's companion and recording. The ADB backend has no disposal hook, so disposal does nothing on Android. The consumer must create its backend from device-mcp 0.7.0 or later to get companion teardown. Consumers must expose a session-owned driver through `getPlatformDriver()` and create a fresh driver/backend for each launch. If launch fails before the driver is exposed, the consumer must dispose that locally held driver in its own failure path. Direct calls to a consumer's `cleanup()` also remain the consumer's responsibility. diff --git a/package.json b/package.json index 6320316..5a6f9ca 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "hono": "^4.12.25" }, "dependencies": { - "@metamask/device-mcp": "^0.6.0", + "@metamask/device-mcp": "^0.7.0", "cosmiconfig": "^9.0.0", "express": "^5.2.1", "zod": "^4.4.3" diff --git a/src/platform/mobile-platform-driver.dispose.test.ts b/src/platform/mobile-platform-driver.dispose.test.ts index d7875ee..e1e7581 100644 --- a/src/platform/mobile-platform-driver.dispose.test.ts +++ b/src/platform/mobile-platform-driver.dispose.test.ts @@ -23,7 +23,7 @@ describe('MobilePlatformDriver disposal', () => { expect(disposeB).toHaveBeenCalledTimes(1); }); - it('supports device-mcp versions without disposal', async () => { + it('completes for backends without a disposal hook', async () => { const driver = new MobilePlatformDriver({ platform: 'ios', } as DeviceBackend); diff --git a/src/platform/mobile-platform-driver.ts b/src/platform/mobile-platform-driver.ts index 7cf96a2..b6f77e9 100644 --- a/src/platform/mobile-platform-driver.ts +++ b/src/platform/mobile-platform-driver.ts @@ -114,17 +114,18 @@ export class MobilePlatformDriver implements IPlatformDriver { this.#excludeDevOverlay = options.excludeDevOverlay ?? true; } - /** Release only this backend's resources; compatible with older device-mcp releases. */ + /** + * Release only this backend's resources. `DeviceBackend.dispose()` is + * optional: backends without it (for example the ADB backend) have nothing + * to release. + */ async dispose(): Promise { - const backend = this.#backend as DeviceBackend & { - dispose?(): Promise; - }; - this.#disposing ??= Promise.resolve() - .then(async () => backend.dispose?.()) - .catch((error: unknown) => { - this.#disposing = undefined; - throw error; - }); + this.#disposing ??= (async (): Promise => { + await this.#backend.dispose?.(); + })().catch((error: unknown) => { + this.#disposing = undefined; + throw error; + }); await this.#disposing; } diff --git a/yarn.lock b/yarn.lock index e19c6f2..b6977ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -926,7 +926,7 @@ __metadata: "@lavamoat/allow-scripts": "npm:^3.0.4" "@lavamoat/preinstall-always-fail": "npm:^2.0.0" "@metamask/auto-changelog": "npm:^5.3.0" - "@metamask/device-mcp": "npm:^0.6.0" + "@metamask/device-mcp": "npm:^0.7.0" "@metamask/eslint-config": "npm:^15.0.0" "@metamask/eslint-config-nodejs": "npm:^15.0.0" "@metamask/eslint-config-typescript": "npm:^15.0.0" @@ -968,15 +968,15 @@ __metadata: languageName: unknown linkType: soft -"@metamask/device-mcp@npm:^0.6.0": - version: 0.6.0 - resolution: "@metamask/device-mcp@npm:0.6.0" +"@metamask/device-mcp@npm:^0.7.0": + version: 0.7.0 + resolution: "@metamask/device-mcp@npm:0.7.0" dependencies: "@modelcontextprotocol/sdk": "npm:^1.12.1" zod: "npm:^4.4.3" bin: device-mcp: ./dist/cli/device-mcp.mjs - checksum: 10/5b9443598050734b8ba6569fe460e5ecad0f8f9301464d22cdcc72561df48d524809ecaa7ec1dab674eabd44ea24e69e36f13b453bb312dc114c7b4b332e92b9 + checksum: 10/6a779002606d52f0be42f3a0f12668b9d277ac40a872334035dab45967c57f135272a3b0e8c3d1027ed5b58dc6440bda4015f4f1535da70868e918010b313111 languageName: node linkType: hard