mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
c573e9c524
## Why Telemetry cannot tell a physical iPhone from a simulator: `platform` is `ios` for both, and the tool-server's `kind` never reaches the events. Physical-iOS support shipped in 0.24.0 with no way to count its adoption except through `ios_device_*` failure stages on `tool:fail`. Physical Android phones have the same gap. ## What `tool:invoke` / `tool:complete` / `tool:fail` gain `device_kind: simulator | emulator | vvd | device | app`, derived from the same device id as `platform` (single classifier in `telemetry-platform.ts`, which `debugger:tool_outcome` and `lens:*` also go through for their platform). `platform: ios` + `device_kind: device` is a physical iPhone. The kind is emitted only for positively recognised id shapes, so the metric is not polluted by ids that are not devices: | id shape | platform | device_kind | |---|---|---| | simulator UUID / `remote:` | ios / ios-remote (tvos when the cache is warm) | simulator | | physical iPhone UDID | ios | device | | `emulator-NNNN` | android (android-tv when warm) | emulator | | USB serial, 6–20 alnum with a digit and a letter | android | device | | non-loopback `ip:port` | android | device (best effort) | | loopback `127.0.0.1:port` / `localhost:port` / `::1:port` | android | emulator | | anything else non-iOS-shaped (Metro 40-hex handle, `booted`, `Pixel_7`, IPv6, mDNS) | android | omitted | | `chromium-cdp-<port>` / `amazon-…` | chromium / vega | app / vvd | | `avdName`-only boot | android | omitted | A child sub-tool that names its own device replaces the parent's platform, kind and provider label wholesale, so an `avdName`-only child never reports the parent's `simulator` next to its own `android`. A logical-keyed Metro session strips the kind. `unknown` (in the registry `DeviceKind`) is rejected by the sanitizer like `platform: "unknown"`; no registry change. ## Querying - Physical iPhone adoption: `tool:invoke` where `platform = 'ios' and device_kind = 'device'`, distinct users over it. `isIosPhysicalUdid` is a positive match, so invoke is clean. - Physical Android: count on `tool:complete` (the device answered). `tool:invoke` / `tool:fail` still carry residual mistakes that happen to look like a serial (`Pixel7`), and a non-loopback `ip:port` may be a networked virtual device. ## Docs - `Telemetry.md` → 1.04, effective date 7 September 2026 (adjust to the release date on merge). Discloses the new category and, while touching that section, the external-provider label that #735 added without a notice line — happy to drop that second bullet if it should ship separately. - No docs-site change: `docs/reference/telemetry.mdx` links to `Telemetry.md` and has no per-property list. ## Notes - Pre-existing, unchanged: HTTP-edge failures carry no `tool_invocation_id`; `stop-all-simulator-servers` declares no capability, so its invoke row has neither field while its fail row has both. `device_kind` follows `platform` in each site. - Legacy 40-hex iPhone UDIDs already classify as `android` today; they now get no kind rather than a wrong one. - Design went through three adversarial review rounds before implementation and one on the diff; the fallback-bucket and child-attribution rules above came out of those. ## Verification - `packages/telemetry`: 319 tests, `packages/tool-server`: full suite 5670 passed / 1 skipped; `typecheck:tests` both packages; `eslint --max-warnings 0`; `knip`; prettier. - New: `test/telemetry-device-kind.test.ts` (the shape table above), plus cases in `http-tools-meta`, `http-platform-alias`, `sanitize`, `registry-listener`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01BSGpeAwvoCv2JAhbLsuto2 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Telemetry now records a device category—such as simulator, emulator, physical device, virtual device, or desktop app—alongside the platform. * Device categories are inferred from device information and may include a short label for external device providers. * Device category information is included in tool invocation, completion, and failure telemetry when available. * Device identifiers themselves continue to be excluded from telemetry. * **Documentation** * Updated the Argent Privacy Notice (Telemetry) to version 1.04, effective 7 September 2026. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
78 lines
3.1 KiB
TypeScript
78 lines
3.1 KiB
TypeScript
import { describe, it, expect, afterEach } from "vitest";
|
|
import { deviceAttributionFromArgs, platformFromArgs } from "../src/http";
|
|
import {
|
|
forgetLogicalKeyedDevice,
|
|
rememberDeviceAlias,
|
|
rememberLogicalKeyedDevice,
|
|
resetDeviceAliases,
|
|
} from "../src/utils/debugger/device-alias";
|
|
|
|
/**
|
|
* The platform on tool:invoke / tool:complete / tool:fail must agree with the
|
|
* platform debugger:tool_outcome derives for the SAME tool_invocation_id.
|
|
* Both now classify through the device-alias map: a forwarded Metro
|
|
* logicalDeviceId (opaque 40-hex handle) is rewritten back to the id the
|
|
* caller connected with before shape classification — otherwise an iOS
|
|
* debugger call joins to `platform: android` lifecycle rows and per-platform
|
|
* failure dashboards double-count.
|
|
*/
|
|
|
|
const LOGICAL_ID = "8b9223b1392be193fa9058e0cef5cefb2bddeb68";
|
|
const IOS_UDID = "BE1DCAD9-43CE-40C4-B8B2-9CB30BC03227";
|
|
const PHYSICAL_UDID = "00008030-000A1B2C3D4E5F60";
|
|
|
|
afterEach(() => {
|
|
resetDeviceAliases();
|
|
});
|
|
|
|
describe("http platform inference and the device alias", () => {
|
|
it("classifies a forwarded logicalDeviceId via its learned alias — ios, not android", () => {
|
|
rememberDeviceAlias(LOGICAL_ID, IOS_UDID);
|
|
expect(platformFromArgs({ device_id: LOGICAL_ID })).toBe("ios");
|
|
});
|
|
|
|
it("an id with no learned alias keeps the shape-based fallback", () => {
|
|
expect(platformFromArgs({ device_id: LOGICAL_ID })).toBe("android");
|
|
});
|
|
|
|
it("stable ids pass through unchanged", () => {
|
|
expect(platformFromArgs({ udid: IOS_UDID })).toBe("ios");
|
|
expect(platformFromArgs({ device_id: "chromium-cdp-9222" })).toBe("chromium");
|
|
});
|
|
|
|
it("derives the device kind from the aliased id, not the opaque handle", () => {
|
|
// A learned alias to a physical iPhone makes the debugger call count as
|
|
// hardware; the 40-hex handle alone never would.
|
|
rememberDeviceAlias(LOGICAL_ID, PHYSICAL_UDID);
|
|
expect(deviceAttributionFromArgs({ device_id: LOGICAL_ID })).toEqual({
|
|
platform: "ios",
|
|
device_kind: "device",
|
|
});
|
|
});
|
|
|
|
it("an un-aliased handle keeps the shape fallback platform and carries no kind", () => {
|
|
// The fallback `android` is pre-existing; what must not happen is the
|
|
// handle being counted as a physical Android phone.
|
|
expect(deviceAttributionFromArgs({ device_id: LOGICAL_ID })).toEqual({ platform: "android" });
|
|
});
|
|
|
|
it("a logical-keyed session carries no kind even if its id looked like a serial", () => {
|
|
// Two devices on one Metro: the caller connects with the logicalDeviceId
|
|
// itself, so there is no alias to canonicalise through. Marked sessions
|
|
// strip the kind regardless of what the handle's shape suggests.
|
|
const serialShaped = "R5CT12345678";
|
|
rememberLogicalKeyedDevice(serialShaped, serialShaped);
|
|
try {
|
|
expect(deviceAttributionFromArgs({ device_id: serialShaped })).toEqual({
|
|
platform: "android",
|
|
});
|
|
} finally {
|
|
forgetLogicalKeyedDevice(serialShaped);
|
|
}
|
|
expect(deviceAttributionFromArgs({ device_id: serialShaped })).toEqual({
|
|
platform: "android",
|
|
device_kind: "device",
|
|
});
|
|
});
|
|
});
|