@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.
- package/dist/{backend-Cbr2tIN-.d.mts → backend-CkJkkw5Z.d.mts} +6 -6
- package/dist/{device-BzPnHvQy.mjs → device-Xsy_LPUF.mjs} +2 -2
- package/dist/device-Xsy_LPUF.mjs.map +1 -0
- package/dist/index.d.mts +12 -12
- package/dist/index.mjs +7 -7
- package/dist/index.mjs.map +1 -1
- package/dist/testing.d.mts +6 -6
- package/dist/testing.mjs +5 -5
- package/dist/testing.mjs.map +1 -1
- package/package.json +15 -1
- package/src/actions.ts +6 -6
- package/src/backend.ts +4 -5
- package/src/backends/agent-device.ts +2 -3
- package/src/backends/ios.ts +4 -4
- package/src/config.ts +4 -5
- package/src/device.ts +3 -3
- package/src/errors.ts +3 -3
- package/src/exec.ts +1 -1
- package/src/index.ts +3 -3
- package/src/lifecycle.ts +5 -5
- package/src/observe.ts +1 -1
- package/src/secrets.ts +2 -2
- package/src/testing.ts +4 -4
- package/dist/device-BzPnHvQy.mjs.map +0 -1
package/dist/testing.d.mts
CHANGED
|
@@ -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-
|
|
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 (
|
|
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
|
|
33
|
-
*
|
|
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 (
|
|
120
|
-
*
|
|
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-
|
|
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
|
|
5
|
-
*
|
|
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 (
|
|
147
|
-
*
|
|
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
|
*/
|
package/dist/testing.mjs.map
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
315
|
-
* action, no caller sleep
|
|
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
|
|
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-*
|
|
143
|
-
//
|
|
144
|
-
// permissions.ts's documented pattern
|
|
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
|
-
//
|
|
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.
|
package/src/backends/ios.ts
CHANGED
|
@@ -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 (
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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 (
|
|
41
|
-
*
|
|
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
|
|
4
|
-
//
|
|
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 (
|
|
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
|
|
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
|
-
//
|
|
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 (
|
|
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
|
-
*
|
|
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.
|
|
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
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
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
|
|
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
|
|
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 (
|
|
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 (
|
|
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
|
|
22
|
-
*
|
|
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 (
|
|
201
|
-
*
|
|
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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"}
|