@phone-use/sdk 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { A as Snapshot, C as AlertAction, D as PressTarget, E as OpenAppResult, T as Capability, j as SnapshotNode, k as ScrollDirection, n as BaseDeviceBackend, w as BackendAlertResult } from "./backend-Cbr2tIN-.mjs";
1
+ import { A as Snapshot, C as AlertAction, D as PressTarget, E as OpenAppResult, T as Capability, j as SnapshotNode, k as ScrollDirection, n as BaseDeviceBackend, w as BackendAlertResult } from "./backend-CkJkkw5Z.mjs";
2
2
  //#region src/exec.d.ts
3
3
  /** Per-call options an {@link ExecRunner} accepts. */
4
4
  type ExecOptions = {
@@ -13,7 +13,7 @@ type ExecResult = {
13
13
  stderr: string;
14
14
  };
15
15
  /**
16
- * The process-execution seam (docs/19: dependency-inject the spawn/exec runner
16
+ * The process-execution seam (dependency-inject the spawn/exec runner
17
17
  * so unit tests run without the real binaries). Runners are dumb: resolve on
18
18
  * exit 0, reject with the execFile error shape otherwise — error normalization
19
19
  * to PhoneUseError happens at the call site, once.
@@ -29,8 +29,8 @@ type FakeCall = {
29
29
  args: unknown[];
30
30
  };
31
31
  /**
32
- * The device-free test double the VPS test tier stands on (docs/20 item 2
33
- * verification protocol). Scripts a sequence of snapshot "screens"; records
32
+ * The device-free test double runs anywhere, no simulator needed.
33
+ * Scripts a sequence of snapshot "screens"; records
34
34
  * every call; lets tests simulate screen transitions per action. Zero deps,
35
35
  * Node-only. Public via the `@phone-use/sdk/testing` subpath so harness tests
36
36
  * and third-party backend authors share it.
@@ -116,8 +116,8 @@ type ScriptedExecRunner = ExecRunner & {
116
116
  remaining(): number;
117
117
  };
118
118
  /**
119
- * The DI'd process runner for lifecycle tests (docs/19: dependency-inject the
120
- * spawn/exec runner). Takes an ordered script of steps; each call consumes the
119
+ * The DI'd process runner for lifecycle tests (the injectable counterpart to
120
+ * defaultExecRunner). Takes an ordered script of steps; each call consumes the
121
121
  * next step, optionally asserting the argv shape, and resolves/rejects in the
122
122
  * execFile-error shape defaultExecRunner produces.
123
123
  */
package/dist/testing.mjs CHANGED
@@ -1,8 +1,8 @@
1
- import { n as BaseDeviceBackend, t as ALL_CAPABILITIES } from "./device-BzPnHvQy.mjs";
1
+ import { n as BaseDeviceBackend, t as ALL_CAPABILITIES } from "./device-Xsy_LPUF.mjs";
2
2
  //#region src/testing.ts
3
3
  /**
4
- * The device-free test double the VPS test tier stands on (docs/20 item 2
5
- * verification protocol). Scripts a sequence of snapshot "screens"; records
4
+ * The device-free test double runs anywhere, no simulator needed.
5
+ * Scripts a sequence of snapshot "screens"; records
6
6
  * every call; lets tests simulate screen transitions per action. Zero deps,
7
7
  * Node-only. Public via the `@phone-use/sdk/testing` subpath so harness tests
8
8
  * and third-party backend authors share it.
@@ -143,8 +143,8 @@ function screen(nodes, app = {}) {
143
143
  };
144
144
  }
145
145
  /**
146
- * The DI'd process runner for lifecycle tests (docs/19: dependency-inject the
147
- * spawn/exec runner). Takes an ordered script of steps; each call consumes the
146
+ * The DI'd process runner for lifecycle tests (the injectable counterpart to
147
+ * defaultExecRunner). Takes an ordered script of steps; each call consumes the
148
148
  * next step, optionally asserting the argv shape, and resolves/rejects in the
149
149
  * execFile-error shape defaultExecRunner produces.
150
150
  */
@@ -1 +1 @@
1
- {"version":3,"file":"testing.mjs","names":[],"sources":["../src/testing.ts"],"sourcesContent":["import { BaseDeviceBackend } from './backend.ts';\nimport type {\n AlertAction,\n BackendAlertResult,\n Capability,\n OpenAppResult,\n PressTarget,\n Rect,\n ScrollDirection,\n Snapshot,\n SnapshotNode,\n} from './device.ts';\nimport { ALL_CAPABILITIES } from './device.ts';\n\n/** One scripted screen in a {@link FakeBackend} sequence (alias of Snapshot). */\nexport type FakeScreen = Snapshot;\n/** A recorded backend call: method name plus the arguments it received. */\nexport type FakeCall = { method: string; args: unknown[] };\n\n/**\n * The device-free test double the VPS test tier stands on (docs/20 item 2\n * verification protocol). Scripts a sequence of snapshot \"screens\"; records\n * every call; lets tests simulate screen transitions per action. Zero deps,\n * Node-only. Public via the `@phone-use/sdk/testing` subpath so harness tests\n * and third-party backend authors share it.\n */\nexport class FakeBackend extends BaseDeviceBackend {\n /** Every backend call made, in order. */\n readonly calls: FakeCall[] = [];\n private screens: FakeScreen[];\n private current = 0;\n private readonly onAction: ((call: FakeCall, current: number) => number | undefined) | undefined;\n private readonly failWith: ((call: FakeCall) => unknown) | undefined;\n\n constructor(opts: {\n screens: FakeScreen[];\n name?: string;\n capabilities?: Iterable<Capability>;\n /** Return a new screen index to simulate a transition caused by the call. */\n onAction?: (call: FakeCall, current: number) => number | undefined;\n /** Throw for matching calls — return the error to throw, undefined to pass. */\n failWith?: (call: FakeCall) => unknown;\n }) {\n super(opts.name ?? 'fake', opts.capabilities ?? ALL_CAPABILITIES);\n if (!opts.screens.length) throw new Error('FakeBackend needs at least one screen');\n this.screens = opts.screens;\n this.onAction = opts.onAction;\n this.failWith = opts.failWith;\n }\n\n /** Jump to screen `i` (throws when out of range). */\n setScreen(i: number): void {\n if (i < 0 || i >= this.screens.length) throw new Error(`no screen ${i}`);\n this.current = i;\n }\n\n /** Index of the screen currently being served. */\n get screenIndex(): number {\n return this.current;\n }\n\n private record(method: string, ...args: unknown[]): FakeCall {\n const call: FakeCall = { method, args };\n this.calls.push(call);\n const err = this.failWith?.(call);\n if (err !== undefined) throw err;\n const next = this.onAction?.(call, this.current);\n if (next !== undefined) this.setScreen(next);\n return call;\n }\n\n override snapshot(opts?: {\n interactiveOnly?: boolean | undefined;\n depth?: number | undefined;\n }): Promise<Snapshot> {\n this.record('snapshot', opts);\n return Promise.resolve(this.screens[this.current]!);\n }\n\n override screenshot(opts: { path: string; overlayRefs?: boolean | undefined }): Promise<{ path: string }> {\n this.record('screenshot', opts);\n return Promise.resolve({ path: opts.path }); // records; writes nothing\n }\n\n override press(target: PressTarget): Promise<void> {\n this.record('press', target);\n return Promise.resolve();\n }\n\n override longPress(ref: string, durationMs?: number): Promise<void> {\n this.record('longPress', ref, durationMs);\n return Promise.resolve();\n }\n\n override fill(ref: string, text: string): Promise<void> {\n this.record('fill', ref, text);\n return Promise.resolve();\n }\n\n override typeText(text: string): Promise<void> {\n this.record('typeText', text);\n return Promise.resolve();\n }\n\n override pressKey(key: 'return'): Promise<void> {\n this.record('pressKey', key);\n return Promise.resolve();\n }\n\n override scroll(direction: ScrollDirection): Promise<void> {\n this.record('scroll', direction);\n return Promise.resolve();\n }\n\n override pan(x: number, y: number, dx: number, dy: number, durationMs?: number): Promise<void> {\n this.record('pan', x, y, dx, dy, durationMs);\n return Promise.resolve();\n }\n\n override waitForText(text: string, timeoutMs?: number): Promise<void> {\n this.record('waitForText', text, timeoutMs);\n return Promise.resolve();\n }\n\n override systemAlert(action: AlertAction): Promise<BackendAlertResult> {\n this.record('systemAlert', action);\n return Promise.resolve({ alert: null });\n }\n\n override home(): Promise<void> {\n this.record('home');\n return Promise.resolve();\n }\n\n override back(): Promise<void> {\n this.record('back');\n return Promise.resolve();\n }\n\n override openApp(opts: {\n app?: string | undefined;\n url?: string | undefined;\n relaunch?: boolean | undefined;\n }): Promise<OpenAppResult> {\n this.record('openApp', opts);\n const s = this.screens[this.current]!;\n return Promise.resolve({ appName: s.appName, appBundleId: s.appBundleId });\n }\n\n override listApps(): Promise<string[]> {\n this.record('listApps');\n return Promise.resolve([]);\n }\n\n override closeSession(): Promise<void> {\n this.record('closeSession');\n return Promise.resolve();\n }\n}\n\nconst DEFAULT_RECT: Rect = { x: 0, y: 100, width: 390, height: 44 };\n\n/** Fixture sugar: an element with sane defaults (Button role, on-screen rect). */\nexport function el(partial: Partial<SnapshotNode> & { ref: string }): SnapshotNode {\n return { type: 'Button', label: '', rect: { ...DEFAULT_RECT }, ...partial };\n}\n\n/** Fixture sugar: a screen with an Application root so viewport detection works. */\nexport function screen(\n nodes: SnapshotNode[],\n app: { appName?: string | undefined; appBundleId?: string | undefined } = {},\n): FakeScreen {\n const root: SnapshotNode = { type: 'Application', rect: { x: 0, y: 0, width: 390, height: 844 } };\n return { nodes: [root, ...nodes], appName: app.appName, appBundleId: app.appBundleId };\n}\n\nexport type { ExecOptions, ExecResult, ExecRunner } from './exec.ts';\n\n/** One step in a {@link scriptedExecRunner} script — what the next exec call gets. */\nexport type ScriptedExecStep = {\n /** Optional argv guard — the step throws if the call doesn't match. */\n expectArgs?: ((file: string, args: string[]) => boolean) | undefined;\n stdout?: string | undefined;\n stderr?: string | undefined;\n /** Nonzero → the call rejects with an execFile-shaped error. */\n code?: number | undefined;\n /** Simulate a runner timeout kill. */\n killed?: boolean | undefined;\n};\n\n/** The runner {@link scriptedExecRunner} returns: an ExecRunner plus call inspection. */\nexport type ScriptedExecRunner = import('./exec.ts').ExecRunner & {\n /** Every exec call made, in order. */\n calls: { file: string; args: string[] }[];\n /** Steps not yet consumed (assert 0 at test end for full-sequence checks). */\n remaining(): number;\n};\n\n/**\n * The DI'd process runner for lifecycle tests (docs/19: dependency-inject the\n * spawn/exec runner). Takes an ordered script of steps; each call consumes the\n * next step, optionally asserting the argv shape, and resolves/rejects in the\n * execFile-error shape defaultExecRunner produces.\n */\nexport function scriptedExecRunner(steps: ScriptedExecStep[]): ScriptedExecRunner {\n const queue = [...steps];\n const calls: { file: string; args: string[] }[] = [];\n const runner = (async (file: string, args: string[]) => {\n calls.push({ file, args: [...args] });\n const step = queue.shift();\n if (!step) throw new Error(`scriptedExecRunner: unexpected call ${file} ${args.join(' ')}`);\n if (step.expectArgs && !step.expectArgs(file, args)) {\n throw new Error(`scriptedExecRunner: argv mismatch for ${file} ${args.join(' ')}`);\n }\n if (step.code !== undefined && step.code !== 0) {\n const err = new Error(`Command failed: ${file} ${args.join(' ')}\\n${step.stderr ?? ''}`) as Error & {\n code: number;\n stdout: string;\n stderr: string;\n killed: boolean;\n };\n err.code = step.code;\n err.stdout = step.stdout ?? '';\n err.stderr = step.stderr ?? '';\n err.killed = step.killed ?? false;\n throw err;\n }\n if (step.killed) {\n const err = new Error(`Command killed: ${file}`) as Error & { killed: boolean; signal: string };\n err.killed = true;\n err.signal = 'SIGTERM';\n throw err;\n }\n return { stdout: step.stdout ?? '', stderr: step.stderr ?? '' };\n }) as ScriptedExecRunner;\n runner.calls = calls;\n runner.remaining = () => queue.length;\n return runner;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"testing.mjs","names":[],"sources":["../src/testing.ts"],"sourcesContent":["import { BaseDeviceBackend } from './backend.ts';\nimport type {\n AlertAction,\n BackendAlertResult,\n Capability,\n OpenAppResult,\n PressTarget,\n Rect,\n ScrollDirection,\n Snapshot,\n SnapshotNode,\n} from './device.ts';\nimport { ALL_CAPABILITIES } from './device.ts';\n\n/** One scripted screen in a {@link FakeBackend} sequence (alias of Snapshot). */\nexport type FakeScreen = Snapshot;\n/** A recorded backend call: method name plus the arguments it received. */\nexport type FakeCall = { method: string; args: unknown[] };\n\n/**\n * The device-free test double — runs anywhere, no simulator needed.\n * Scripts a sequence of snapshot \"screens\"; records\n * every call; lets tests simulate screen transitions per action. Zero deps,\n * Node-only. Public via the `@phone-use/sdk/testing` subpath so harness tests\n * and third-party backend authors share it.\n */\nexport class FakeBackend extends BaseDeviceBackend {\n /** Every backend call made, in order. */\n readonly calls: FakeCall[] = [];\n private screens: FakeScreen[];\n private current = 0;\n private readonly onAction: ((call: FakeCall, current: number) => number | undefined) | undefined;\n private readonly failWith: ((call: FakeCall) => unknown) | undefined;\n\n constructor(opts: {\n screens: FakeScreen[];\n name?: string;\n capabilities?: Iterable<Capability>;\n /** Return a new screen index to simulate a transition caused by the call. */\n onAction?: (call: FakeCall, current: number) => number | undefined;\n /** Throw for matching calls — return the error to throw, undefined to pass. */\n failWith?: (call: FakeCall) => unknown;\n }) {\n super(opts.name ?? 'fake', opts.capabilities ?? ALL_CAPABILITIES);\n if (!opts.screens.length) throw new Error('FakeBackend needs at least one screen');\n this.screens = opts.screens;\n this.onAction = opts.onAction;\n this.failWith = opts.failWith;\n }\n\n /** Jump to screen `i` (throws when out of range). */\n setScreen(i: number): void {\n if (i < 0 || i >= this.screens.length) throw new Error(`no screen ${i}`);\n this.current = i;\n }\n\n /** Index of the screen currently being served. */\n get screenIndex(): number {\n return this.current;\n }\n\n private record(method: string, ...args: unknown[]): FakeCall {\n const call: FakeCall = { method, args };\n this.calls.push(call);\n const err = this.failWith?.(call);\n if (err !== undefined) throw err;\n const next = this.onAction?.(call, this.current);\n if (next !== undefined) this.setScreen(next);\n return call;\n }\n\n override snapshot(opts?: {\n interactiveOnly?: boolean | undefined;\n depth?: number | undefined;\n }): Promise<Snapshot> {\n this.record('snapshot', opts);\n return Promise.resolve(this.screens[this.current]!);\n }\n\n override screenshot(opts: { path: string; overlayRefs?: boolean | undefined }): Promise<{ path: string }> {\n this.record('screenshot', opts);\n return Promise.resolve({ path: opts.path }); // records; writes nothing\n }\n\n override press(target: PressTarget): Promise<void> {\n this.record('press', target);\n return Promise.resolve();\n }\n\n override longPress(ref: string, durationMs?: number): Promise<void> {\n this.record('longPress', ref, durationMs);\n return Promise.resolve();\n }\n\n override fill(ref: string, text: string): Promise<void> {\n this.record('fill', ref, text);\n return Promise.resolve();\n }\n\n override typeText(text: string): Promise<void> {\n this.record('typeText', text);\n return Promise.resolve();\n }\n\n override pressKey(key: 'return'): Promise<void> {\n this.record('pressKey', key);\n return Promise.resolve();\n }\n\n override scroll(direction: ScrollDirection): Promise<void> {\n this.record('scroll', direction);\n return Promise.resolve();\n }\n\n override pan(x: number, y: number, dx: number, dy: number, durationMs?: number): Promise<void> {\n this.record('pan', x, y, dx, dy, durationMs);\n return Promise.resolve();\n }\n\n override waitForText(text: string, timeoutMs?: number): Promise<void> {\n this.record('waitForText', text, timeoutMs);\n return Promise.resolve();\n }\n\n override systemAlert(action: AlertAction): Promise<BackendAlertResult> {\n this.record('systemAlert', action);\n return Promise.resolve({ alert: null });\n }\n\n override home(): Promise<void> {\n this.record('home');\n return Promise.resolve();\n }\n\n override back(): Promise<void> {\n this.record('back');\n return Promise.resolve();\n }\n\n override openApp(opts: {\n app?: string | undefined;\n url?: string | undefined;\n relaunch?: boolean | undefined;\n }): Promise<OpenAppResult> {\n this.record('openApp', opts);\n const s = this.screens[this.current]!;\n return Promise.resolve({ appName: s.appName, appBundleId: s.appBundleId });\n }\n\n override listApps(): Promise<string[]> {\n this.record('listApps');\n return Promise.resolve([]);\n }\n\n override closeSession(): Promise<void> {\n this.record('closeSession');\n return Promise.resolve();\n }\n}\n\nconst DEFAULT_RECT: Rect = { x: 0, y: 100, width: 390, height: 44 };\n\n/** Fixture sugar: an element with sane defaults (Button role, on-screen rect). */\nexport function el(partial: Partial<SnapshotNode> & { ref: string }): SnapshotNode {\n return { type: 'Button', label: '', rect: { ...DEFAULT_RECT }, ...partial };\n}\n\n/** Fixture sugar: a screen with an Application root so viewport detection works. */\nexport function screen(\n nodes: SnapshotNode[],\n app: { appName?: string | undefined; appBundleId?: string | undefined } = {},\n): FakeScreen {\n const root: SnapshotNode = { type: 'Application', rect: { x: 0, y: 0, width: 390, height: 844 } };\n return { nodes: [root, ...nodes], appName: app.appName, appBundleId: app.appBundleId };\n}\n\nexport type { ExecOptions, ExecResult, ExecRunner } from './exec.ts';\n\n/** One step in a {@link scriptedExecRunner} script — what the next exec call gets. */\nexport type ScriptedExecStep = {\n /** Optional argv guard — the step throws if the call doesn't match. */\n expectArgs?: ((file: string, args: string[]) => boolean) | undefined;\n stdout?: string | undefined;\n stderr?: string | undefined;\n /** Nonzero → the call rejects with an execFile-shaped error. */\n code?: number | undefined;\n /** Simulate a runner timeout kill. */\n killed?: boolean | undefined;\n};\n\n/** The runner {@link scriptedExecRunner} returns: an ExecRunner plus call inspection. */\nexport type ScriptedExecRunner = import('./exec.ts').ExecRunner & {\n /** Every exec call made, in order. */\n calls: { file: string; args: string[] }[];\n /** Steps not yet consumed (assert 0 at test end for full-sequence checks). */\n remaining(): number;\n};\n\n/**\n * The DI'd process runner for lifecycle tests (the injectable counterpart to\n * defaultExecRunner). Takes an ordered script of steps; each call consumes the\n * next step, optionally asserting the argv shape, and resolves/rejects in the\n * execFile-error shape defaultExecRunner produces.\n */\nexport function scriptedExecRunner(steps: ScriptedExecStep[]): ScriptedExecRunner {\n const queue = [...steps];\n const calls: { file: string; args: string[] }[] = [];\n const runner = (async (file: string, args: string[]) => {\n calls.push({ file, args: [...args] });\n const step = queue.shift();\n if (!step) throw new Error(`scriptedExecRunner: unexpected call ${file} ${args.join(' ')}`);\n if (step.expectArgs && !step.expectArgs(file, args)) {\n throw new Error(`scriptedExecRunner: argv mismatch for ${file} ${args.join(' ')}`);\n }\n if (step.code !== undefined && step.code !== 0) {\n const err = new Error(`Command failed: ${file} ${args.join(' ')}\\n${step.stderr ?? ''}`) as Error & {\n code: number;\n stdout: string;\n stderr: string;\n killed: boolean;\n };\n err.code = step.code;\n err.stdout = step.stdout ?? '';\n err.stderr = step.stderr ?? '';\n err.killed = step.killed ?? false;\n throw err;\n }\n if (step.killed) {\n const err = new Error(`Command killed: ${file}`) as Error & { killed: boolean; signal: string };\n err.killed = true;\n err.signal = 'SIGTERM';\n throw err;\n }\n return { stdout: step.stdout ?? '', stderr: step.stderr ?? '' };\n }) as ScriptedExecRunner;\n runner.calls = calls;\n runner.remaining = () => queue.length;\n return runner;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,15 +1,29 @@
1
1
  {
2
2
  "name": "@phone-use/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Typed mobile-device SDK — Device, backends, errors, actions",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "sideEffects": false,
8
+ "homepage": "https://www.phoneuse.dev",
9
+ "bugs": {
10
+ "url": "https://github.com/Rajmeet/phone-use/issues"
11
+ },
8
12
  "repository": {
9
13
  "type": "git",
10
14
  "url": "git+https://github.com/Rajmeet/phone-use.git",
11
15
  "directory": "packages/sdk"
12
16
  },
17
+ "keywords": [
18
+ "ios",
19
+ "simulator",
20
+ "mobile",
21
+ "device",
22
+ "automation",
23
+ "agent",
24
+ "ai",
25
+ "testing"
26
+ ],
13
27
  "publishConfig": {
14
28
  "access": "public",
15
29
  "registry": "https://registry.npmjs.org"
package/src/actions.ts CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  import { SecretStore } from './secrets.ts';
18
18
 
19
19
  // ---------------------------------------------------------------------------
20
- // The observe→act seam (docs/19 §API shape; docs/20 item 4): observe() returns
20
+ // The observe→act seam: observe() returns
21
21
  // portable Action descriptors carrying RE-RESOLVABLE element queries; act()
22
22
  // re-resolves against the live tree and executes with no re-inference. A
23
23
  // compiled skill is a stored Action[]. The resolution ladder (DeviceCore) is
@@ -63,13 +63,13 @@ export type ActionVerb =
63
63
  | 'waitForText';
64
64
 
65
65
  /**
66
- * The observe→act seam (docs/19 §API shape; docs/20 item 4): a portable action
66
+ * The observe→act seam: a portable action
67
67
  * descriptor carrying a re-resolvable {@link ElementQuery}. `observe()` returns
68
68
  * these; `act()` re-resolves against the live tree and executes with no
69
69
  * re-inference. A compiled skill is a stored `Action[]`.
70
70
  */
71
71
  export type Action = {
72
- /** Versioned, documented UNSTABLE pre-1.0 (docs/20 open-question 3). */
72
+ /** Versioned, documented UNSTABLE pre-1.0. */
73
73
  formatVersion: 0;
74
74
  /** What to do. */
75
75
  verb: ActionVerb;
@@ -106,7 +106,7 @@ export type Action = {
106
106
  | undefined;
107
107
  };
108
108
 
109
- /** The stored-Action[] artifact (public TYPE, unstable FORMAT — see docs/20). */
109
+ /** The stored-Action[] artifact (public TYPE, unstable FORMAT pre-1.0). */
110
110
  export type CompiledSkill = {
111
111
  formatVersion: 0;
112
112
  name: string;
@@ -311,8 +311,8 @@ type WaitOutcome =
311
311
  | { ok: false; result: ActionResult };
312
312
 
313
313
  /**
314
- * Resolve + auto-wait (docs/19: visible+hittable+enabled+settled on every
315
- * action, no caller sleep; docs/20 risk 2: must not double latency).
314
+ * Resolve + auto-wait (visible+hittable+enabled+settled on every
315
+ * action, no caller sleep and it must not double latency).
316
316
  * Fast path: a fresh cache resolving to an actionable target executes with
317
317
  * ZERO extra snapshots. Slow path: poll in place (never scroll — scrolling is
318
318
  * the ladder's job, and polling must not dismiss transient menus) until the
package/src/backend.ts CHANGED
@@ -11,7 +11,7 @@ import type {
11
11
  import { PhoneUseError, UnsupportedCapabilityError } from './errors.ts';
12
12
 
13
13
  /**
14
- * The backend contract (docs/19 §Backend contract): a STRUCTURAL interface —
14
+ * The backend contract: a STRUCTURAL interface —
15
15
  * any object with these methods is a backend — plus an optional
16
16
  * {@link BaseDeviceBackend} class with the plumbing done (Appium BaseDriver
17
17
  * ergonomics). Methods reject only with PhoneUseError subclasses; backends
@@ -139,10 +139,9 @@ export abstract class BaseDeviceBackend implements DeviceBackend {
139
139
  }
140
140
 
141
141
  // ---------------------------------------------------------------------------
142
- // Runtime backend registry (registerBackend + phone-backend-* convention,
143
- // docs/19). A deliberate module-level Map: this IS the registry — mirrored on
144
- // permissions.ts's documented pattern; the item-2 grep audit is scoped to
145
- // driver code.
142
+ // Runtime backend registry (registerBackend + the phone-backend-* naming
143
+ // convention). A deliberate module-level Map: this IS the registry — mirrored
144
+ // on permissions.ts's documented pattern.
146
145
  // ---------------------------------------------------------------------------
147
146
 
148
147
  /** Builds a backend from an optional {@link DeviceConfig}. */
@@ -19,9 +19,8 @@ import { toPhoneUseError } from '../errors.ts';
19
19
  // pinning is per-request in agent-device (AgentDeviceSelectionOptions), so a
20
20
  // backend holds a selection object and spreads it into every call. With no
21
21
  // config, selection is {} and the client is default-constructed — requests are
22
- // byte-identical to the pre-seam process-global path (booted-sim auto-detect),
23
- // which is the item-2 backward-compat guarantee (docs/20 risk: default-
24
- // selection drift).
22
+ // byte-identical to the pre-seam process-global path (booted-sim auto-detect)
23
+ // the backward-compat guarantee against default-selection drift.
25
24
  //
26
25
  // Every method normalizes errors via toPhoneUseError — no agent-device type
27
26
  // or error ever escapes this file.
@@ -5,12 +5,12 @@ import { createDeviceHandle, type Device } from '../lifecycle.ts';
5
5
  import { createAgentDeviceBackend } from './agent-device.ts';
6
6
 
7
7
  // ---------------------------------------------------------------------------
8
- // The iOS engine (docs/19 §Lifecycle, engine-as-object): ios.launch() creates
8
+ // The iOS engine (engine-as-object): ios.launch() creates
9
9
  // and boots a DEDICATED simulator via simctl — no more "whatever is booted" —
10
10
  // and returns a Device whose backend is pinned to that udid. ios.connect()
11
11
  // reattaches; its no-arg form is the sole survivor of the old booted-sim
12
12
  // auto-detect. Scripts never branch on locality: connect(endpoint) for cloud
13
- // devices is reserved API (docs/20 kill list — local-only this cycle).
13
+ // devices is reserved API — local-only for now.
14
14
  //
15
15
  // Created sims are named `phone-use-<hex>` deliberately: if a process is
16
16
  // kill -9'd, the in-process reaper can't run, and the name prefix is how
@@ -40,7 +40,7 @@ type CommonIosOptions = {
40
40
  * open lazily and a probe requires the daemon to exist.
41
41
  */
42
42
  failFast?: boolean | undefined;
43
- /** @internal test seam — DI'd process runner (docs/19 test strategy). */
43
+ /** @internal test seam — DI'd process runner. */
44
44
  exec?: ExecRunner | undefined;
45
45
  };
46
46
 
@@ -73,7 +73,7 @@ function simctlArgs(setPath: string | undefined, args: string[]): string[] {
73
73
 
74
74
  // Exit 149 = "operation not allowed in current state" (already booted /
75
75
  // already shut down). Code first, stderr regex as fallback — Apple rewords
76
- // messages; the numeric code is a Mac-verification item (docs/20 as-built).
76
+ // messages; the numeric code is the stable signal.
77
77
  function isAlreadyInState(err: unknown): boolean {
78
78
  if (!isExecError(err)) return false;
79
79
  if (err.code === 149) return true;
package/src/config.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  // Device configuration: a discriminated union, never a stringly-typed
3
- // capability blob (docs/19 §Backend contract, learning from Appium's leaks).
3
+ // capability blob (learning from Appium's leaks).
4
4
  // Field names verified against agent-device 0.19.3's AgentDeviceSelectionOptions
5
- // and AgentDeviceClientConfig. Plain types — no zod in the SDK public API
6
- // (docs/20 risk 6).
5
+ // and AgentDeviceClientConfig. Plain types — no zod in the SDK public API.
7
6
  // ---------------------------------------------------------------------------
8
7
 
9
8
  /** Fields shared by every platform's device config (session/daemon pinning). */
@@ -37,7 +36,7 @@ export type AndroidDeviceConfig = CommonDeviceConfig & {
37
36
 
38
37
  /**
39
38
  * Device configuration: a discriminated union on `platform`, never a
40
- * stringly-typed capability blob (docs/19 §Backend contract, learning from
41
- * Appium's leaks). Plain types — no zod in the SDK public API.
39
+ * stringly-typed capability blob (learning from Appium's leaks).
40
+ * Plain types — no zod in the SDK public API.
42
41
  */
43
42
  export type DeviceConfig = IosDeviceConfig | AndroidDeviceConfig;
package/src/device.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  // Shared device types — the SDK's own vocabulary, structurally compatible with
3
- // agent-device's snapshot nodes but never importing its types (docs/20 item 2:
4
- // no backend type in the public API). Every optional is `| undefined` so
3
+ // agent-device's snapshot nodes but never importing its types no backend
4
+ // type in the public API. Every optional is `| undefined` so
5
5
  // narrower backend types assign structurally under exactOptionalPropertyTypes.
6
6
  // ---------------------------------------------------------------------------
7
7
 
@@ -11,7 +11,7 @@ export type Rect = { x: number; y: number; width: number; height: number };
11
11
  /**
12
12
  * One accessibility-tree node as reported by a backend snapshot. The SDK's own
13
13
  * vocabulary — structurally compatible with agent-device's nodes but never
14
- * importing its types (docs/20 item 2: no backend type in the public API).
14
+ * importing its types (no backend type in the public API).
15
15
  */
16
16
  export type SnapshotNode = {
17
17
  ref?: string | undefined;
package/src/errors.ts CHANGED
@@ -28,7 +28,7 @@ export type PhoneUseErrorDetails = Record<string, unknown> & {
28
28
  };
29
29
 
30
30
  /**
31
- * Root of the typed error tree (docs/19 §Artifacts, errors; docs/20 item 2).
31
+ * Root of the typed error tree.
32
32
  * Every backend method rejects only with PhoneUseError subclasses; agent-device
33
33
  * (or any transport) errors are normalized at the boundary by
34
34
  * {@link toPhoneUseError} so no backend type ever reaches the public API. Each
@@ -143,8 +143,8 @@ export function toPhoneUseError(
143
143
  const message = e.message || String(e.code ?? 'agent-device error');
144
144
  const details: PhoneUseErrorDetails = { ...(e.details ?? {}) };
145
145
  const opts = { details, cause: err };
146
- // Timeout is a shape, not a code, in agent-device — heuristic on message.
147
- // Flagged in docs/20: Mac verification is the arbiter for this mapping.
146
+ // Timeout is a shape, not a code, in agent-device — heuristic on message,
147
+ // verified against real agent-device errors on macOS.
148
148
  if (TIMEOUT_RE.test(message)) return new TimeoutError(message, opts);
149
149
  switch (e.code) {
150
150
  case 'DEVICE_NOT_FOUND':
package/src/exec.ts CHANGED
@@ -13,7 +13,7 @@ export type ExecOptions = {
13
13
  export type ExecResult = { stdout: string; stderr: string };
14
14
 
15
15
  /**
16
- * The process-execution seam (docs/19: dependency-inject the spawn/exec runner
16
+ * The process-execution seam (dependency-inject the spawn/exec runner
17
17
  * so unit tests run without the real binaries). Runners are dumb: resolve on
18
18
  * exit 0, reject with the execFile error shape otherwise — error normalization
19
19
  * to PhoneUseError happens at the call site, once.
package/src/index.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * @phone-use/sdk — the device runtime SDK: engine-as-object lifecycle
3
- * (ios.launch/connect → Device), Device backends, config, errors, capabilities
4
- * (docs/20-runtime-sdk-v1-plan.md items 2-3; action verbs land in item 4).
3
+ * (ios.launch/connect → Device), Device backends, config, errors, capabilities,
4
+ * and the action verb surface.
5
5
  *
6
6
  * The test double (FakeBackend) lives on the "@phone-use/sdk/testing" subpath,
7
7
  * deliberately not re-exported here.
8
8
  */
9
9
  /** The published package version (kept in sync with package.json by the release flow). */
10
- export const VERSION = '0.1.0';
10
+ export const VERSION = '0.1.1';
11
11
 
12
12
  export {
13
13
  type Action,
package/src/lifecycle.ts CHANGED
@@ -19,10 +19,10 @@ export type DevicePlatform = 'ios' | 'android';
19
19
  export type DeviceStatus = 'running' | 'closed';
20
20
 
21
21
  /**
22
- * The Device lifecycle handle (docs/20 item 3; docs/19 §Lifecycle): id, pinned
23
- * backend, close/dispose, idle lease + reaper — plus the action verb surface
24
- * (item 4) layered onto the same type. `ios.launch()` and `ios.connect()`
25
- * return it; the android engine (item 7b) will share `createDeviceHandle`.
22
+ * The Device lifecycle handle: id, pinned backend, close/dispose, idle lease +
23
+ * reaper — plus the action verb surface layered onto the same type.
24
+ * `ios.launch()` and `ios.connect()` return it; the future android engine will
25
+ * share `createDeviceHandle`.
26
26
  */
27
27
  export interface Device {
28
28
  /** udid (iOS) / serial (Android). */
@@ -53,7 +53,7 @@ export interface Device {
53
53
  /** `await using` support — delegates to {@link Device.close}. */
54
54
  [Symbol.asyncDispose](): Promise<void>;
55
55
 
56
- // --- the action surface (docs/20 item 4): flat hot path -------------------
56
+ // --- the action surface: flat hot path ------------------------------------
57
57
  /** Look at the screen: elements + rendered text + portable Action[]. */
58
58
  observe(opts?: { signal?: AbortSignal | undefined }): Promise<ObserveResult>;
59
59
  /** Tap by label/id query. Auto-waits; never throws for normal outcomes. */
package/src/observe.ts CHANGED
@@ -3,7 +3,7 @@ import type { Rect, SnapshotNode } from './device.ts';
3
3
  import { SessionNotFoundError } from './errors.ts';
4
4
 
5
5
  // ---------------------------------------------------------------------------
6
- // DeviceCore (docs/20 item 4): the one-brain observe/resolve/act core, moved
6
+ // DeviceCore: the one-brain observe/resolve/act core, moved
7
7
  // verbatim from the harness's DeviceContext. Everything here is portable —
8
8
  // no runtime-specific globals and no image libraries — so it runs under Node. The
9
9
  // module level holds only types, pure functions, and read-only lookup tables;
package/src/secrets.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // ---------------------------------------------------------------------------
2
- // %variable% secret substitution (docs/19 §API shape, Stagehand's pattern):
2
+ // %variable% secret substitution (Stagehand's pattern):
3
3
  // the model/caller plans against NAMES; values are injected at the last moment
4
4
  // before backend.fill/typeText and never rendered into Actions, results,
5
5
  // observations, or traces. Redaction is best-effort belt-and-braces — after
@@ -12,7 +12,7 @@
12
12
  const MIN_SECRET_LENGTH = 4;
13
13
 
14
14
  /**
15
- * `%variable%` secret substitution (docs/19 §API shape, Stagehand's pattern):
15
+ * `%variable%` secret substitution (Stagehand's pattern):
16
16
  * the model/caller plans against NAMES; values are injected at the last moment
17
17
  * before backend.fill/typeText and never rendered into Actions, results,
18
18
  * observations, or traces. Redaction is best-effort belt-and-braces; the hard
package/src/testing.ts CHANGED
@@ -18,8 +18,8 @@ export type FakeScreen = Snapshot;
18
18
  export type FakeCall = { method: string; args: unknown[] };
19
19
 
20
20
  /**
21
- * The device-free test double the VPS test tier stands on (docs/20 item 2
22
- * verification protocol). Scripts a sequence of snapshot "screens"; records
21
+ * The device-free test double runs anywhere, no simulator needed.
22
+ * Scripts a sequence of snapshot "screens"; records
23
23
  * every call; lets tests simulate screen transitions per action. Zero deps,
24
24
  * Node-only. Public via the `@phone-use/sdk/testing` subpath so harness tests
25
25
  * and third-party backend authors share it.
@@ -197,8 +197,8 @@ export type ScriptedExecRunner = import('./exec.ts').ExecRunner & {
197
197
  };
198
198
 
199
199
  /**
200
- * The DI'd process runner for lifecycle tests (docs/19: dependency-inject the
201
- * spawn/exec runner). Takes an ordered script of steps; each call consumes the
200
+ * The DI'd process runner for lifecycle tests (the injectable counterpart to
201
+ * defaultExecRunner). Takes an ordered script of steps; each call consumes the
202
202
  * next step, optionally asserting the argv shape, and resolves/rejects in the
203
203
  * execFile-error shape defaultExecRunner produces.
204
204
  */
@@ -1 +0,0 @@
1
- {"version":3,"file":"device-BzPnHvQy.mjs","names":[],"sources":["../src/errors.ts","../src/backend.ts","../src/device.ts"],"sourcesContent":["import { isAgentDeviceError } from 'agent-device';\n\n/**\n * Stable machine-readable error codes carried by every {@link PhoneUseError}.\n *\n * Codes are deliberately string-identical to agent-device's where a concept\n * maps 1:1 (DEVICE_NOT_FOUND, SESSION_NOT_FOUND, ...) so existing harness\n * checks like `(e as {code?: string}).code === 'SESSION_NOT_FOUND'` keep\n * working unchanged across the normalization boundary.\n */\nexport type PhoneUseErrorCode =\n | 'DEVICE_NOT_FOUND'\n | 'DEVICE_IN_USE'\n | 'SESSION_NOT_FOUND'\n | 'TIMEOUT'\n | 'ACTION_FAILED'\n | 'UNSUPPORTED_CAPABILITY'\n | 'BACKEND_NOT_FOUND'\n | 'ABORTED'\n | 'UNKNOWN';\n\n/** Structured, code-specific context attached to a {@link PhoneUseError}. */\nexport type PhoneUseErrorDetails = Record<string, unknown> & {\n /** Preserved from agent-device details.hint — describeError renders it. */\n hint?: string | undefined;\n /** The raw backend code when we collapse to ACTION_FAILED. */\n backendCode?: string | undefined;\n};\n\n/**\n * Root of the typed error tree (docs/19 §Artifacts, errors; docs/20 item 2).\n * Every backend method rejects only with PhoneUseError subclasses; agent-device\n * (or any transport) errors are normalized at the boundary by\n * {@link toPhoneUseError} so no backend type ever reaches the public API. Each\n * error carries a stable `code` and an explicit `retryable` flag — the field\n * agent loops need to decide retry-vs-replan.\n */\nexport class PhoneUseError extends Error {\n /** Stable machine-readable code — the field to branch on. */\n readonly code: PhoneUseErrorCode;\n /** Whether retrying the same call can plausibly succeed. */\n readonly retryable: boolean;\n /** Optional structured context (hint, raw backend code, ...). */\n readonly details?: PhoneUseErrorDetails | undefined;\n\n constructor(\n message: string,\n opts: {\n code: PhoneUseErrorCode;\n retryable: boolean;\n details?: PhoneUseErrorDetails | undefined;\n cause?: unknown;\n },\n ) {\n super(message, opts.cause === undefined ? undefined : { cause: opts.cause });\n this.name = new.target.name;\n this.code = opts.code;\n this.retryable = opts.retryable;\n this.details = opts.details;\n }\n}\n\n/** No device matched the selection (`DEVICE_NOT_FOUND`, not retryable). */\nexport class DeviceNotFoundError extends PhoneUseError {\n constructor(message: string, opts: { details?: PhoneUseErrorDetails | undefined; cause?: unknown } = {}) {\n super(message, { code: 'DEVICE_NOT_FOUND', retryable: false, ...opts });\n }\n}\n\n/** The device is held by another session (`DEVICE_IN_USE`, retryable). */\nexport class DeviceInUseError extends PhoneUseError {\n constructor(message: string, opts: { details?: PhoneUseErrorDetails | undefined; cause?: unknown } = {}) {\n super(message, { code: 'DEVICE_IN_USE', retryable: true, ...opts });\n }\n}\n\n/** No live session — the device/session was closed (`SESSION_NOT_FOUND`, not retryable). */\nexport class SessionNotFoundError extends PhoneUseError {\n constructor(message: string, opts: { details?: PhoneUseErrorDetails | undefined; cause?: unknown } = {}) {\n super(message, { code: 'SESSION_NOT_FOUND', retryable: false, ...opts });\n }\n}\n\n/** A deadline elapsed before the operation completed (`TIMEOUT`, retryable). */\nexport class TimeoutError extends PhoneUseError {\n constructor(message: string, opts: { details?: PhoneUseErrorDetails | undefined; cause?: unknown } = {}) {\n super(message, { code: 'TIMEOUT', retryable: true, ...opts });\n }\n}\n\n/** A device action failed to execute (`ACTION_FAILED`, retryable). */\nexport class ActionFailedError extends PhoneUseError {\n constructor(message: string, opts: { details?: PhoneUseErrorDetails | undefined; cause?: unknown } = {}) {\n super(message, { code: 'ACTION_FAILED', retryable: true, ...opts });\n }\n}\n\n/** The backend does not implement the capability (`UNSUPPORTED_CAPABILITY`, not retryable). */\nexport class UnsupportedCapabilityError extends PhoneUseError {\n /** The backend that rejected the call. */\n readonly backend: string;\n /** The missing capability. */\n readonly capability: string;\n\n constructor(opts: { backend: string; capability: string; cause?: unknown }) {\n super(`backend \"${opts.backend}\" does not support \"${opts.capability}\"`, {\n code: 'UNSUPPORTED_CAPABILITY',\n retryable: false,\n details: { backend: opts.backend, capability: opts.capability },\n ...(opts.cause === undefined ? {} : { cause: opts.cause }),\n });\n this.backend = opts.backend;\n this.capability = opts.capability;\n }\n}\n\n/**\n * The caller's AbortSignal fired (`ABORTED`, not retryable). Abort is caller\n * control flow, not a device outcome — never retried.\n */\nexport class AbortedError extends PhoneUseError {\n constructor(message = 'aborted by caller', opts: { cause?: unknown } = {}) {\n super(message, { code: 'ABORTED', retryable: false, ...opts });\n }\n}\n\nconst TIMEOUT_RE = /\\btime[d ]?\\s?out\\b|\\btimeout\\b/i;\n\n/**\n * Boundary normalizer: ANY thrown value → PhoneUseError. agent-device AppError\n * codes map per the table below; an existing PhoneUseError passes through\n * untouched; unknown values wrap as UNKNOWN (not retryable). The original\n * error always rides on `cause`; details (including hint) are preserved.\n */\nexport function toPhoneUseError(\n err: unknown,\n ctx: { backend?: string; capability?: string } = {},\n): PhoneUseError {\n if (err instanceof PhoneUseError) return err;\n\n if (isAgentDeviceError(err)) {\n const e = err as Error & { code?: string; details?: Record<string, unknown> };\n const message = e.message || String(e.code ?? 'agent-device error');\n const details: PhoneUseErrorDetails = { ...(e.details ?? {}) };\n const opts = { details, cause: err };\n // Timeout is a shape, not a code, in agent-device — heuristic on message.\n // Flagged in docs/20: Mac verification is the arbiter for this mapping.\n if (TIMEOUT_RE.test(message)) return new TimeoutError(message, opts);\n switch (e.code) {\n case 'DEVICE_NOT_FOUND':\n return new DeviceNotFoundError(message, opts);\n case 'DEVICE_IN_USE':\n return new DeviceInUseError(message, opts);\n case 'SESSION_NOT_FOUND':\n return new SessionNotFoundError(message, opts);\n case 'UNSUPPORTED_PLATFORM':\n case 'UNSUPPORTED_OPERATION':\n case 'NOT_IMPLEMENTED':\n return new UnsupportedCapabilityError({\n backend: ctx.backend ?? 'unknown',\n capability: ctx.capability ?? String(e.code),\n cause: err,\n });\n default:\n return new ActionFailedError(message, {\n details: { ...details, backendCode: e.code === undefined ? undefined : String(e.code) },\n cause: err,\n });\n }\n }\n\n if (err instanceof Error) {\n if (TIMEOUT_RE.test(err.message)) return new TimeoutError(err.message, { cause: err });\n return new PhoneUseError(err.message, { code: 'UNKNOWN', retryable: false, cause: err });\n }\n return new PhoneUseError(String(err), { code: 'UNKNOWN', retryable: false, cause: err });\n}\n","import type { DeviceConfig } from './config.ts';\nimport type {\n AlertAction,\n BackendAlertResult,\n Capability,\n OpenAppResult,\n PressTarget,\n ScrollDirection,\n Snapshot,\n} from './device.ts';\nimport { PhoneUseError, UnsupportedCapabilityError } from './errors.ts';\n\n/**\n * The backend contract (docs/19 §Backend contract): a STRUCTURAL interface —\n * any object with these methods is a backend — plus an optional\n * {@link BaseDeviceBackend} class with the plumbing done (Appium BaseDriver\n * ergonomics). Methods reject only with PhoneUseError subclasses; backends\n * normalize their transport's errors at the boundary.\n */\nexport interface DeviceBackend {\n /** Stable backend identifier, e.g. `\"agent-device\"`. */\n readonly backendName: string;\n /** The capabilities this backend actually implements. */\n readonly capabilities: ReadonlySet<Capability>;\n\n /** Capture the accessibility tree of the frontmost app. */\n snapshot(opts?: { interactiveOnly?: boolean | undefined; depth?: number | undefined }): Promise<Snapshot>;\n /** Save a screenshot to `path` (optionally with `@ref` overlays drawn). */\n screenshot(opts: { path: string; overlayRefs?: boolean | undefined }): Promise<{ path: string }>;\n /** Tap an element ref or raw coordinates. */\n press(target: PressTarget): Promise<void>;\n /** Long-press an element ref. */\n longPress(ref: string, durationMs?: number): Promise<void>;\n /** Focus an element and replace its text. */\n fill(ref: string, text: string): Promise<void>;\n /** Type into whatever currently has keyboard focus. */\n typeText(text: string): Promise<void>;\n /** Press a hardware/keyboard key (Return only, this cycle). */\n pressKey(key: 'return'): Promise<void>;\n /** Scroll the active scroll view one step. */\n scroll(direction: ScrollDirection): Promise<void>;\n /** Coordinate drag: touch down at (x,y), move by (dx,dy). Operates picker wheels, sliders, carousels. */\n pan(x: number, y: number, dx: number, dy: number, durationMs?: number): Promise<void>;\n /** Block until `text` appears on screen or the timeout elapses. */\n waitForText(text: string, timeoutMs?: number): Promise<void>;\n /** Read/accept/dismiss the app's own alert via the transport's alert command. */\n systemAlert(action: AlertAction): Promise<BackendAlertResult>;\n /** Go to the home screen. */\n home(): Promise<void>;\n /** Navigate back (hardware back / nav-bar back). */\n back(): Promise<void>;\n /** Open an app by name/bundle id, or a URL (deep link). */\n openApp(opts: {\n app?: string | undefined;\n url?: string | undefined;\n relaunch?: boolean | undefined;\n }): Promise<OpenAppResult>;\n /** List installed app bundle ids. */\n listApps(): Promise<string[]>;\n /** Close the transport session (idempotent; safe when none is open). */\n closeSession(): Promise<void>;\n}\n\n/**\n * Optional plumbing base: every method rejects with a typed\n * UnsupportedCapabilityError until overridden. Subclasses declare their\n * capability set and override exactly what they support.\n */\nexport abstract class BaseDeviceBackend implements DeviceBackend {\n readonly backendName: string;\n readonly capabilities: ReadonlySet<Capability>;\n\n protected constructor(backendName: string, capabilities: Iterable<Capability>) {\n this.backendName = backendName;\n this.capabilities = new Set(capabilities);\n }\n\n protected unsupported(capability: Capability): UnsupportedCapabilityError {\n return new UnsupportedCapabilityError({ backend: this.backendName, capability });\n }\n\n /** Throws UnsupportedCapabilityError unless this backend declares `capability`. */\n requireCapability(capability: Capability): void {\n if (!this.capabilities.has(capability)) throw this.unsupported(capability);\n }\n\n snapshot(_opts?: { interactiveOnly?: boolean | undefined; depth?: number | undefined }): Promise<Snapshot> {\n return Promise.reject(this.unsupported('snapshot'));\n }\n screenshot(_opts: { path: string; overlayRefs?: boolean | undefined }): Promise<{ path: string }> {\n return Promise.reject(this.unsupported('screenshot'));\n }\n press(_target: PressTarget): Promise<void> {\n return Promise.reject(this.unsupported('press'));\n }\n longPress(_ref: string, _durationMs?: number): Promise<void> {\n return Promise.reject(this.unsupported('longPress'));\n }\n fill(_ref: string, _text: string): Promise<void> {\n return Promise.reject(this.unsupported('fill'));\n }\n typeText(_text: string): Promise<void> {\n return Promise.reject(this.unsupported('type'));\n }\n pressKey(_key: 'return'): Promise<void> {\n return Promise.reject(this.unsupported('key'));\n }\n scroll(_direction: ScrollDirection): Promise<void> {\n return Promise.reject(this.unsupported('scroll'));\n }\n pan(_x: number, _y: number, _dx: number, _dy: number, _durationMs?: number): Promise<void> {\n return Promise.reject(this.unsupported('pan'));\n }\n waitForText(_text: string, _timeoutMs?: number): Promise<void> {\n return Promise.reject(this.unsupported('waitForText'));\n }\n systemAlert(_action: AlertAction): Promise<BackendAlertResult> {\n return Promise.reject(this.unsupported('alert'));\n }\n home(): Promise<void> {\n return Promise.reject(this.unsupported('home'));\n }\n back(): Promise<void> {\n return Promise.reject(this.unsupported('back'));\n }\n openApp(_opts: {\n app?: string | undefined;\n url?: string | undefined;\n relaunch?: boolean | undefined;\n }): Promise<OpenAppResult> {\n return Promise.reject(this.unsupported('openApp'));\n }\n listApps(): Promise<string[]> {\n return Promise.reject(this.unsupported('listApps'));\n }\n closeSession(): Promise<void> {\n return Promise.reject(this.unsupported('closeSession'));\n }\n}\n\n// ---------------------------------------------------------------------------\n// Runtime backend registry (registerBackend + phone-backend-* convention,\n// docs/19). A deliberate module-level Map: this IS the registry — mirrored on\n// permissions.ts's documented pattern; the item-2 grep audit is scoped to\n// driver code.\n// ---------------------------------------------------------------------------\n\n/** Builds a backend from an optional {@link DeviceConfig}. */\nexport type BackendFactory = (config?: DeviceConfig) => DeviceBackend | Promise<DeviceBackend>;\n\nconst factories = new Map<string, BackendFactory>();\n\n/**\n * Register a backend factory under a unique name (the `phone-backend-*`\n * convention for third parties). Throws if the name is already taken.\n */\nexport function registerBackend(name: string, factory: BackendFactory): void {\n if (factories.has(name)) {\n throw new PhoneUseError(`backend \"${name}\" is already registered`, {\n code: 'ACTION_FAILED',\n retryable: false,\n });\n }\n factories.set(name, factory);\n}\n\n/** Look up a registered factory by name; throws `BACKEND_NOT_FOUND` if absent. */\nexport function getBackendFactory(name: string): BackendFactory {\n const factory = factories.get(name);\n if (!factory) {\n throw new PhoneUseError(\n `no backend named \"${name}\" — registered: ${[...factories.keys()].join(', ') || '(none)'}`,\n {\n code: 'BACKEND_NOT_FOUND',\n retryable: false,\n },\n );\n }\n return factory;\n}\n\n/** Names of every registered backend, in registration order. */\nexport function listBackends(): string[] {\n return [...factories.keys()];\n}\n","// ---------------------------------------------------------------------------\n// Shared device types — the SDK's own vocabulary, structurally compatible with\n// agent-device's snapshot nodes but never importing its types (docs/20 item 2:\n// no backend type in the public API). Every optional is `| undefined` so\n// narrower backend types assign structurally under exactOptionalPropertyTypes.\n// ---------------------------------------------------------------------------\n\n/** Element geometry in screen points (same space as screenshot pixels at @1x). */\nexport type Rect = { x: number; y: number; width: number; height: number };\n\n/**\n * One accessibility-tree node as reported by a backend snapshot. The SDK's own\n * vocabulary — structurally compatible with agent-device's nodes but never\n * importing its types (docs/20 item 2: no backend type in the public API).\n */\nexport type SnapshotNode = {\n ref?: string | undefined;\n type?: string | undefined;\n role?: string | undefined;\n label?: string | undefined;\n value?: string | undefined;\n identifier?: string | undefined;\n enabled?: boolean | undefined;\n selected?: boolean | undefined;\n focused?: boolean | undefined;\n interactionBlocked?: string | undefined;\n rect?: Rect | undefined;\n};\n\n/** One backend snapshot: the node list plus the frontmost app when known. */\nexport type Snapshot = {\n nodes: SnapshotNode[];\n appName?: string | undefined;\n appBundleId?: string | undefined;\n};\n\n/** Scroll gesture direction. */\nexport type ScrollDirection = 'up' | 'down' | 'left' | 'right';\n\n/** A press target: an element ref from a snapshot, or raw screen coordinates. */\nexport type PressTarget = { ref: string } | { x: number; y: number };\n\n/** What to do with a system alert: read it, accept it, or dismiss it. */\nexport type AlertAction = 'get' | 'accept' | 'dismiss';\n\n/** Raw result of a backend's system-alert command. */\nexport type BackendAlertResult = {\n alert?:\n | { title?: string | undefined; message?: string | undefined; buttons?: string[] | undefined }\n | null\n | undefined;\n handled?: boolean | undefined;\n button?: string | undefined;\n};\n\n/** What a backend reports after opening an app or URL. */\nexport type OpenAppResult = { appName?: string | undefined; appBundleId?: string | undefined };\n\n/** A backend feature a caller can query via `backend.capabilities`. */\nexport type Capability =\n | 'snapshot'\n | 'screenshot'\n | 'press'\n | 'longPress'\n | 'fill'\n | 'type'\n | 'key'\n | 'scroll'\n | 'pan'\n | 'waitForText'\n | 'alert'\n | 'home'\n | 'back'\n | 'openApp'\n | 'openUrl'\n | 'listApps'\n | 'closeSession';\n\n/** Every capability — what a full backend (agent-device iOS) declares. */\nexport const ALL_CAPABILITIES: readonly Capability[] = [\n 'snapshot',\n 'screenshot',\n 'press',\n 'longPress',\n 'fill',\n 'type',\n 'key',\n 'scroll',\n 'pan',\n 'waitForText',\n 'alert',\n 'home',\n 'back',\n 'openApp',\n 'openUrl',\n 'listApps',\n 'closeSession',\n];\n"],"mappings":"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"}