agent-device 0.7.21 → 0.8.0

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.
Files changed (135) hide show
  1. package/README.md +86 -0
  2. package/dist/src/224.js +2 -0
  3. package/dist/src/274.js +1 -0
  4. package/dist/src/331.js +3 -0
  5. package/dist/src/bin.d.ts +1 -0
  6. package/dist/src/bin.js +65 -61
  7. package/dist/src/cli-client-commands.d.ts +8 -0
  8. package/dist/src/cli.d.ts +6 -0
  9. package/dist/src/client-normalizers.d.ts +21 -0
  10. package/dist/src/client-types.d.ts +267 -0
  11. package/dist/src/client.d.ts +5 -0
  12. package/dist/src/core/app-events.d.ts +8 -0
  13. package/dist/src/core/batch.d.ts +17 -0
  14. package/dist/src/core/capabilities.d.ts +3 -0
  15. package/dist/src/core/dispatch-payload.d.ts +1 -0
  16. package/dist/src/core/dispatch-resolve.d.ts +28 -0
  17. package/dist/src/core/dispatch-series.d.ts +7 -0
  18. package/dist/src/core/dispatch.d.ts +34 -0
  19. package/dist/src/core/open-target.d.ts +4 -0
  20. package/dist/src/core/settings-contract.d.ts +8 -0
  21. package/dist/src/daemon/action-utils.d.ts +3 -0
  22. package/dist/src/daemon/app-log-android.d.ts +4 -0
  23. package/dist/src/daemon/app-log-ios.d.ts +6 -0
  24. package/dist/src/daemon/app-log-process.d.ts +15 -0
  25. package/dist/src/daemon/app-log-stream.d.ts +19 -0
  26. package/dist/src/daemon/app-log.d.ts +28 -0
  27. package/dist/src/daemon/artifact-archive.d.ts +12 -0
  28. package/dist/src/daemon/artifact-download.d.ts +12 -0
  29. package/dist/src/daemon/artifact-materialization.d.ts +17 -0
  30. package/dist/src/daemon/artifact-registry.d.ts +12 -0
  31. package/dist/src/daemon/config.d.ts +16 -0
  32. package/dist/src/daemon/context.d.ts +22 -0
  33. package/dist/src/daemon/device-ready.d.ts +6 -0
  34. package/dist/src/daemon/handlers/find.d.ts +40 -0
  35. package/dist/src/daemon/handlers/install-source.d.ts +10 -0
  36. package/dist/src/daemon/handlers/interaction.d.ts +14 -0
  37. package/dist/src/daemon/handlers/lease.d.ts +8 -0
  38. package/dist/src/daemon/handlers/parse-utils.d.ts +3 -0
  39. package/dist/src/daemon/handlers/record-trace.d.ts +15 -0
  40. package/dist/src/daemon/handlers/session-replay-heal.d.ts +8 -0
  41. package/dist/src/daemon/handlers/session-replay-script.d.ts +3 -0
  42. package/dist/src/daemon/handlers/session.d.ts +67 -0
  43. package/dist/src/daemon/handlers/snapshot.d.ts +32 -0
  44. package/dist/src/daemon/http-server.d.ts +26 -0
  45. package/dist/src/daemon/is-predicates.d.ts +14 -0
  46. package/dist/src/daemon/lease-context.d.ts +9 -0
  47. package/dist/src/daemon/lease-registry.d.ts +63 -0
  48. package/dist/src/daemon/materialized-path-registry.d.ts +15 -0
  49. package/dist/src/daemon/network-log.d.ts +32 -0
  50. package/dist/src/daemon/request-cancel.d.ts +9 -0
  51. package/dist/src/daemon/request-lock-policy.d.ts +2 -0
  52. package/dist/src/daemon/request-router.d.ts +17 -0
  53. package/dist/src/daemon/runtime-hints.d.ts +19 -0
  54. package/dist/src/daemon/script-utils.d.ts +15 -0
  55. package/dist/src/daemon/scroll-planner.d.ts +12 -0
  56. package/dist/src/daemon/selectors.d.ts +65 -0
  57. package/dist/src/daemon/server-lifecycle.d.ts +23 -0
  58. package/dist/src/daemon/session-routing.d.ts +3 -0
  59. package/dist/src/daemon/session-selector.d.ts +10 -0
  60. package/dist/src/daemon/session-store.d.ts +32 -0
  61. package/dist/src/daemon/snapshot-diff.d.ts +20 -0
  62. package/dist/src/daemon/snapshot-processing.d.ts +8 -0
  63. package/dist/src/daemon/transport.d.ts +6 -0
  64. package/dist/src/daemon/types.d.ts +118 -0
  65. package/dist/src/daemon/upload-registry.d.ts +7 -0
  66. package/dist/src/daemon/upload.d.ts +5 -0
  67. package/dist/src/daemon-client.d.ts +40 -0
  68. package/dist/src/daemon.d.ts +1 -0
  69. package/dist/src/daemon.js +36 -36
  70. package/dist/src/index.d.ts +4 -0
  71. package/dist/src/index.js +1 -0
  72. package/dist/src/platforms/android/adb.d.ts +5 -0
  73. package/dist/src/platforms/android/app-lifecycle.d.ts +30 -0
  74. package/dist/src/platforms/android/device-input-state.d.ts +19 -0
  75. package/dist/src/platforms/android/devices.d.ts +22 -0
  76. package/dist/src/platforms/android/index.d.ts +7 -0
  77. package/dist/src/platforms/android/input-actions.d.ts +12 -0
  78. package/dist/src/platforms/android/install-artifact.d.ts +11 -0
  79. package/dist/src/platforms/android/manifest.d.ts +1 -0
  80. package/dist/src/platforms/android/notifications.d.ts +11 -0
  81. package/dist/src/platforms/android/open-target.d.ts +4 -0
  82. package/dist/src/platforms/android/settings.d.ts +3 -0
  83. package/dist/src/platforms/android/snapshot.d.ts +8 -0
  84. package/dist/src/platforms/android/ui-hierarchy.d.ts +21 -0
  85. package/dist/src/platforms/appearance.d.ts +2 -0
  86. package/dist/src/platforms/boot-diagnostics.d.ts +14 -0
  87. package/dist/src/platforms/install-source.d.ts +26 -0
  88. package/dist/src/platforms/ios/apps.d.ts +34 -0
  89. package/dist/src/platforms/ios/config.d.ts +9 -0
  90. package/dist/src/platforms/ios/devicectl.d.ts +13 -0
  91. package/dist/src/platforms/ios/devices.d.ts +39 -0
  92. package/dist/src/platforms/ios/ensure-simulator.d.ts +18 -0
  93. package/dist/src/platforms/ios/index.d.ts +3 -0
  94. package/dist/src/platforms/ios/install-artifact.d.ts +18 -0
  95. package/dist/src/platforms/ios/launch-diagnostics.d.ts +11 -0
  96. package/dist/src/platforms/ios/plist.d.ts +1 -0
  97. package/dist/src/platforms/ios/runner-client.d.ts +36 -0
  98. package/dist/src/platforms/ios/runner-errors.d.ts +20 -0
  99. package/dist/src/platforms/ios/runner-session.d.ts +25 -0
  100. package/dist/src/platforms/ios/runner-transport.d.ts +10 -0
  101. package/dist/src/platforms/ios/runner-xctestrun.d.ts +18 -0
  102. package/dist/src/platforms/ios/screenshot.d.ts +13 -0
  103. package/dist/src/platforms/ios/simctl.d.ts +7 -0
  104. package/dist/src/platforms/ios/simulator.d.ts +11 -0
  105. package/dist/src/platforms/permission-utils.d.ts +9 -0
  106. package/dist/src/upload-client.d.ts +7 -0
  107. package/dist/src/utils/args.d.ts +27 -0
  108. package/dist/src/utils/cli-config.d.ts +9 -0
  109. package/dist/src/utils/cli-option-schema.d.ts +19 -0
  110. package/dist/src/utils/cli-options.d.ts +13 -0
  111. package/dist/src/utils/command-schema.d.ts +98 -0
  112. package/dist/src/utils/device-isolation.d.ts +3 -0
  113. package/dist/src/utils/device.d.ts +27 -0
  114. package/dist/src/utils/diagnostics.d.ts +30 -0
  115. package/dist/src/utils/errors.d.ts +26 -0
  116. package/dist/src/utils/exec.d.ts +32 -0
  117. package/dist/src/utils/finders.d.ts +12 -0
  118. package/dist/src/utils/interactive.d.ts +1 -0
  119. package/dist/src/utils/interactors.d.ts +31 -0
  120. package/dist/src/utils/json-input.d.ts +1 -0
  121. package/dist/src/utils/keyed-lock.d.ts +1 -0
  122. package/dist/src/utils/output.d.ts +25 -0
  123. package/dist/src/utils/payload-input.d.ts +12 -0
  124. package/dist/src/utils/process-identity.d.ts +11 -0
  125. package/dist/src/utils/retry.d.ts +54 -0
  126. package/dist/src/utils/session-binding.d.ts +18 -0
  127. package/dist/src/utils/snapshot-lines.d.ts +12 -0
  128. package/dist/src/utils/snapshot.d.ts +42 -0
  129. package/dist/src/utils/timeouts.d.ts +3 -0
  130. package/dist/src/utils/version.d.ts +2 -0
  131. package/package.json +9 -1
  132. package/skills/agent-device/SKILL.md +36 -0
  133. package/skills/agent-device/references/remote-tenancy.md +11 -0
  134. package/skills/agent-device/references/session-management.md +37 -1
  135. package/dist/src/678.js +0 -3
@@ -0,0 +1,13 @@
1
+ import type { CliFlags } from './command-schema.ts';
2
+ type EnvMap = Record<string, string | undefined>;
3
+ export declare function resolveCliOptions(argv: string[], options?: {
4
+ cwd?: string;
5
+ env?: EnvMap;
6
+ strictFlags?: boolean;
7
+ }): {
8
+ command: string | null;
9
+ positionals: string[];
10
+ flags: CliFlags;
11
+ warnings: string[];
12
+ };
13
+ export {};
@@ -0,0 +1,98 @@
1
+ export type CliFlags = {
2
+ json: boolean;
3
+ config?: string;
4
+ stateDir?: string;
5
+ daemonBaseUrl?: string;
6
+ daemonAuthToken?: string;
7
+ daemonTransport?: 'auto' | 'socket' | 'http';
8
+ daemonServerMode?: 'socket' | 'http' | 'dual';
9
+ tenant?: string;
10
+ sessionIsolation?: 'none' | 'tenant';
11
+ runId?: string;
12
+ leaseId?: string;
13
+ sessionLock?: 'reject' | 'strip';
14
+ sessionLocked?: boolean;
15
+ sessionLockConflicts?: 'reject' | 'strip';
16
+ platform?: 'ios' | 'android' | 'apple';
17
+ target?: 'mobile' | 'tv';
18
+ device?: string;
19
+ udid?: string;
20
+ serial?: string;
21
+ iosSimulatorDeviceSet?: string;
22
+ androidDeviceAllowlist?: string;
23
+ out?: string;
24
+ session?: string;
25
+ runtime?: string;
26
+ metroHost?: string;
27
+ metroPort?: number;
28
+ bundleUrl?: string;
29
+ launchUrl?: string;
30
+ boot?: boolean;
31
+ reuseExisting?: boolean;
32
+ verbose?: boolean;
33
+ snapshotInteractiveOnly?: boolean;
34
+ snapshotCompact?: boolean;
35
+ snapshotDepth?: number;
36
+ snapshotScope?: string;
37
+ snapshotRaw?: boolean;
38
+ appsFilter?: 'user-installed' | 'all';
39
+ count?: number;
40
+ fps?: number;
41
+ intervalMs?: number;
42
+ holdMs?: number;
43
+ jitterPx?: number;
44
+ doubleTap?: boolean;
45
+ pauseMs?: number;
46
+ pattern?: 'one-way' | 'ping-pong';
47
+ activity?: string;
48
+ saveScript?: boolean | string;
49
+ shutdown?: boolean;
50
+ relaunch?: boolean;
51
+ headless?: boolean;
52
+ restart?: boolean;
53
+ noRecord?: boolean;
54
+ replayUpdate?: boolean;
55
+ steps?: string;
56
+ stepsFile?: string;
57
+ batchOnError?: 'stop';
58
+ batchMaxSteps?: number;
59
+ batchSteps?: Array<{
60
+ command: string;
61
+ positionals?: string[];
62
+ flags?: Record<string, unknown>;
63
+ }>;
64
+ help: boolean;
65
+ version: boolean;
66
+ };
67
+ export type FlagKey = keyof CliFlags;
68
+ type FlagType = 'boolean' | 'int' | 'enum' | 'string' | 'booleanOrString';
69
+ export type FlagDefinition = {
70
+ key: FlagKey;
71
+ names: readonly string[];
72
+ type: FlagType;
73
+ enumValues?: readonly string[];
74
+ min?: number;
75
+ max?: number;
76
+ setValue?: CliFlags[FlagKey];
77
+ usageLabel?: string;
78
+ usageDescription?: string;
79
+ };
80
+ type CommandSchema = {
81
+ description: string;
82
+ positionalArgs: readonly string[];
83
+ allowsExtraPositionals?: boolean;
84
+ allowedFlags: readonly FlagKey[];
85
+ defaults?: Partial<CliFlags>;
86
+ skipCapabilityCheck?: boolean;
87
+ usageOverride?: string;
88
+ };
89
+ export declare const GLOBAL_FLAG_KEYS: Set<keyof CliFlags>;
90
+ export declare function getFlagDefinition(token: string): FlagDefinition | undefined;
91
+ export declare function getFlagDefinitions(): readonly FlagDefinition[];
92
+ export declare function getCommandSchema(command: string | null): CommandSchema | undefined;
93
+ export declare function getCliCommandNames(): string[];
94
+ export declare function getSchemaCapabilityKeys(): string[];
95
+ export declare function isStrictFlagModeEnabled(value: string | undefined): boolean;
96
+ export declare function buildUsageText(): string;
97
+ export declare function buildCommandUsageText(commandName: string): string | null;
98
+ export {};
@@ -0,0 +1,3 @@
1
+ export declare function resolveIosSimulatorDeviceSetPath(flagValue: string | undefined, env?: NodeJS.ProcessEnv): string | undefined;
2
+ export declare function parseSerialAllowlist(value: string): Set<string>;
3
+ export declare function resolveAndroidSerialAllowlist(flagValue: string | undefined, env?: NodeJS.ProcessEnv): ReadonlySet<string> | undefined;
@@ -0,0 +1,27 @@
1
+ export type Platform = 'ios' | 'android';
2
+ export type PlatformSelector = Platform | 'apple';
3
+ export type DeviceKind = 'simulator' | 'emulator' | 'device';
4
+ export type DeviceTarget = 'mobile' | 'tv';
5
+ export type DeviceInfo = {
6
+ platform: Platform;
7
+ id: string;
8
+ name: string;
9
+ kind: DeviceKind;
10
+ target?: DeviceTarget;
11
+ booted?: boolean;
12
+ simulatorSetPath?: string;
13
+ };
14
+ type DeviceSelector = {
15
+ platform?: Platform;
16
+ target?: DeviceTarget;
17
+ deviceName?: string;
18
+ udid?: string;
19
+ serial?: string;
20
+ };
21
+ type DeviceSelectionContext = {
22
+ simulatorSetPath?: string;
23
+ };
24
+ export declare function normalizePlatformSelector(platform: PlatformSelector | undefined): Platform | undefined;
25
+ export declare function resolveApplePlatformName(target: DeviceTarget | undefined): 'iOS' | 'tvOS';
26
+ export declare function selectDevice(devices: DeviceInfo[], selector: DeviceSelector, context?: DeviceSelectionContext): Promise<DeviceInfo>;
27
+ export {};
@@ -0,0 +1,30 @@
1
+ type DiagnosticLevel = 'info' | 'warn' | 'error' | 'debug';
2
+ type DiagnosticsScopeOptions = {
3
+ session?: string;
4
+ requestId?: string;
5
+ command?: string;
6
+ debug?: boolean;
7
+ logPath?: string;
8
+ traceLogPath?: string;
9
+ };
10
+ export declare function createRequestId(): string;
11
+ export declare function withDiagnosticsScope<T>(options: DiagnosticsScopeOptions, fn: () => Promise<T> | T): Promise<T>;
12
+ export declare function getDiagnosticsMeta(): {
13
+ diagnosticId?: string;
14
+ requestId?: string;
15
+ session?: string;
16
+ command?: string;
17
+ debug?: boolean;
18
+ };
19
+ export declare function emitDiagnostic(event: {
20
+ level?: DiagnosticLevel;
21
+ phase: string;
22
+ durationMs?: number;
23
+ data?: Record<string, unknown>;
24
+ }): void;
25
+ export declare function withDiagnosticTimer<T>(phase: string, fn: () => Promise<T> | T, data?: Record<string, unknown>): Promise<T>;
26
+ export declare function flushDiagnosticsToSessionFile(options?: {
27
+ force?: boolean;
28
+ }): string | null;
29
+ export declare function redactDiagnosticData<T>(input: T): T;
30
+ export {};
@@ -0,0 +1,26 @@
1
+ type ErrorCode = 'INVALID_ARGS' | 'DEVICE_NOT_FOUND' | 'TOOL_MISSING' | 'APP_NOT_INSTALLED' | 'UNSUPPORTED_PLATFORM' | 'UNSUPPORTED_OPERATION' | 'COMMAND_FAILED' | 'SESSION_NOT_FOUND' | 'UNAUTHORIZED' | 'UNKNOWN';
2
+ type AppErrorDetails = Record<string, unknown> & {
3
+ hint?: string;
4
+ diagnosticId?: string;
5
+ logPath?: string;
6
+ };
7
+ export type NormalizedError = {
8
+ code: string;
9
+ message: string;
10
+ hint?: string;
11
+ diagnosticId?: string;
12
+ logPath?: string;
13
+ details?: Record<string, unknown>;
14
+ };
15
+ export declare class AppError extends Error {
16
+ code: ErrorCode;
17
+ details?: AppErrorDetails;
18
+ cause?: unknown;
19
+ constructor(code: ErrorCode, message: string, details?: AppErrorDetails, cause?: unknown);
20
+ }
21
+ export declare function asAppError(err: unknown): AppError;
22
+ export declare function normalizeError(err: unknown, context?: {
23
+ diagnosticId?: string;
24
+ logPath?: string;
25
+ }): NormalizedError;
26
+ export {};
@@ -0,0 +1,32 @@
1
+ import { spawn } from 'node:child_process';
2
+ export type ExecResult = {
3
+ stdout: string;
4
+ stderr: string;
5
+ exitCode: number;
6
+ stdoutBuffer?: Buffer;
7
+ };
8
+ type ExecOptions = {
9
+ cwd?: string;
10
+ env?: NodeJS.ProcessEnv;
11
+ allowFailure?: boolean;
12
+ binaryStdout?: boolean;
13
+ stdin?: string | Buffer;
14
+ timeoutMs?: number;
15
+ detached?: boolean;
16
+ };
17
+ type ExecStreamOptions = ExecOptions & {
18
+ onStdoutChunk?: (chunk: string) => void;
19
+ onStderrChunk?: (chunk: string) => void;
20
+ onSpawn?: (child: ReturnType<typeof spawn>) => void;
21
+ };
22
+ export type ExecBackgroundResult = {
23
+ child: ReturnType<typeof spawn>;
24
+ wait: Promise<ExecResult>;
25
+ };
26
+ export declare function runCmd(cmd: string, args: string[], options?: ExecOptions): Promise<ExecResult>;
27
+ export declare function whichCmd(cmd: string): Promise<boolean>;
28
+ export declare function runCmdSync(cmd: string, args: string[], options?: ExecOptions): ExecResult;
29
+ export declare function runCmdDetached(cmd: string, args: string[], options?: ExecOptions): void;
30
+ export declare function runCmdStreaming(cmd: string, args: string[], options?: ExecStreamOptions): Promise<ExecResult>;
31
+ export declare function runCmdBackground(cmd: string, args: string[], options?: ExecOptions): ExecBackgroundResult;
32
+ export {};
@@ -0,0 +1,12 @@
1
+ import type { SnapshotNode } from './snapshot.ts';
2
+ export type FindLocator = 'any' | 'text' | 'label' | 'value' | 'role' | 'id';
3
+ type FindMatchOptions = {
4
+ requireRect?: boolean;
5
+ };
6
+ type FindBestMatches = {
7
+ matches: SnapshotNode[];
8
+ score: number;
9
+ };
10
+ export declare function findNodeByLocator(nodes: SnapshotNode[], locator: FindLocator, query: string, options?: FindMatchOptions): SnapshotNode | null;
11
+ export declare function findBestMatchesByLocator(nodes: SnapshotNode[], locator: FindLocator, query: string, options?: FindMatchOptions): FindBestMatches;
12
+ export {};
@@ -0,0 +1 @@
1
+ export declare function isInteractive(): boolean;
@@ -0,0 +1,31 @@
1
+ import type { DeviceInfo } from './device.ts';
2
+ export type RunnerContext = {
3
+ requestId?: string;
4
+ appBundleId?: string;
5
+ verbose?: boolean;
6
+ logPath?: string;
7
+ traceLogPath?: string;
8
+ };
9
+ type Interactor = {
10
+ open(app: string, options?: {
11
+ activity?: string;
12
+ appBundleId?: string;
13
+ url?: string;
14
+ }): Promise<void>;
15
+ openDevice(): Promise<void>;
16
+ close(app: string): Promise<void>;
17
+ tap(x: number, y: number): Promise<void>;
18
+ doubleTap(x: number, y: number): Promise<void>;
19
+ swipe(x1: number, y1: number, x2: number, y2: number, durationMs?: number): Promise<void>;
20
+ longPress(x: number, y: number, durationMs?: number): Promise<void>;
21
+ focus(x: number, y: number): Promise<void>;
22
+ type(text: string): Promise<void>;
23
+ fill(x: number, y: number, text: string): Promise<void>;
24
+ scroll(direction: string, amount?: number): Promise<void>;
25
+ scrollIntoView(text: string): Promise<{
26
+ attempts?: number;
27
+ } | void>;
28
+ screenshot(outPath: string, appBundleId?: string): Promise<void>;
29
+ };
30
+ export declare function getInteractor(device: DeviceInfo, runnerContext: RunnerContext): Interactor;
31
+ export {};
@@ -0,0 +1 @@
1
+ export declare function looksLikeInlineJson(value: string): boolean;
@@ -0,0 +1 @@
1
+ export declare function withKeyedLock<T>(locks: Map<string, Promise<unknown>>, key: string, task: () => Promise<T>): Promise<T>;
@@ -0,0 +1,25 @@
1
+ import { AppError, type NormalizedError } from './errors.ts';
2
+ type JsonResult = {
3
+ success: true;
4
+ data?: Record<string, unknown>;
5
+ } | {
6
+ success: false;
7
+ error: {
8
+ code: string;
9
+ message: string;
10
+ hint?: string;
11
+ diagnosticId?: string;
12
+ logPath?: string;
13
+ details?: Record<string, unknown>;
14
+ };
15
+ };
16
+ export declare function printJson(result: JsonResult): void;
17
+ export declare function printHumanError(err: AppError | NormalizedError, options?: {
18
+ showDetails?: boolean;
19
+ }): void;
20
+ export declare function formatSnapshotText(data: Record<string, unknown>, options?: {
21
+ raw?: boolean;
22
+ flatten?: boolean;
23
+ }): string;
24
+ export declare function formatSnapshotDiffText(data: Record<string, unknown>): string;
25
+ export {};
@@ -0,0 +1,12 @@
1
+ export type ResolvedPayloadInput = {
2
+ kind: 'file';
3
+ path: string;
4
+ } | {
5
+ kind: 'inline';
6
+ text: string;
7
+ };
8
+ export declare function resolvePayloadInput(value: string, options?: {
9
+ subject?: string;
10
+ cwd?: string;
11
+ expandPath?: (value: string, cwd?: string) => string;
12
+ }): ResolvedPayloadInput;
@@ -0,0 +1,11 @@
1
+ export declare function isProcessAlive(pid: number): boolean;
2
+ export declare function readProcessStartTime(pid: number): string | null;
3
+ export declare function readProcessCommand(pid: number): string | null;
4
+ export declare function isAgentDeviceDaemonCommand(command: string): boolean;
5
+ export declare function isAgentDeviceDaemonProcess(pid: number, expectedStartTime?: string): boolean;
6
+ export declare function waitForProcessExit(pid: number, timeoutMs: number): Promise<boolean>;
7
+ export declare function stopProcessForTakeover(pid: number, options: {
8
+ termTimeoutMs: number;
9
+ killTimeoutMs: number;
10
+ expectedStartTime?: string;
11
+ }): Promise<void>;
@@ -0,0 +1,54 @@
1
+ type RetryOptions = {
2
+ attempts?: number;
3
+ baseDelayMs?: number;
4
+ maxDelayMs?: number;
5
+ jitter?: number;
6
+ shouldRetry?: (error: unknown, attempt: number) => boolean;
7
+ };
8
+ type RetryPolicy = {
9
+ maxAttempts: number;
10
+ baseDelayMs: number;
11
+ maxDelayMs: number;
12
+ jitter: number;
13
+ shouldRetry?: (error: unknown, attempt: number) => boolean;
14
+ };
15
+ type RetryAttemptContext = {
16
+ attempt: number;
17
+ maxAttempts: number;
18
+ deadline?: Deadline;
19
+ };
20
+ type TimeoutProfile = {
21
+ startupMs: number;
22
+ operationMs: number;
23
+ totalMs: number;
24
+ };
25
+ type RetryTelemetryEvent = {
26
+ phase?: string;
27
+ event: 'attempt_failed' | 'retry_scheduled' | 'succeeded' | 'exhausted';
28
+ attempt: number;
29
+ maxAttempts: number;
30
+ delayMs?: number;
31
+ elapsedMs?: number;
32
+ remainingMs?: number;
33
+ reason?: string;
34
+ };
35
+ export declare function isEnvTruthy(value: string | undefined): boolean;
36
+ export declare const TIMEOUT_PROFILES: Record<string, TimeoutProfile>;
37
+ export declare class Deadline {
38
+ private readonly startedAtMs;
39
+ private readonly expiresAtMs;
40
+ private constructor();
41
+ static fromTimeoutMs(timeoutMs: number, nowMs?: number): Deadline;
42
+ remainingMs(nowMs?: number): number;
43
+ elapsedMs(nowMs?: number): number;
44
+ isExpired(nowMs?: number): boolean;
45
+ }
46
+ export declare function retryWithPolicy<T>(fn: (context: RetryAttemptContext) => Promise<T>, policy?: Partial<RetryPolicy>, options?: {
47
+ deadline?: Deadline;
48
+ phase?: string;
49
+ signal?: AbortSignal;
50
+ classifyReason?: (error: unknown) => string | undefined;
51
+ onEvent?: (event: RetryTelemetryEvent) => void;
52
+ }): Promise<T>;
53
+ export declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
54
+ export {};
@@ -0,0 +1,18 @@
1
+ import type { CliFlags } from './command-schema.ts';
2
+ import type { DaemonLockPolicy } from '../daemon/types.ts';
3
+ export type BindingSettings = {
4
+ defaultPlatform?: CliFlags['platform'];
5
+ lockPolicy?: DaemonLockPolicy;
6
+ };
7
+ type BindingPolicyOverrides = Pick<Partial<CliFlags>, 'sessionLock' | 'sessionLocked' | 'sessionLockConflicts'>;
8
+ type LockableFlags = Pick<Partial<CliFlags>, 'platform' | 'target' | 'device' | 'udid' | 'serial' | 'iosSimulatorDeviceSet' | 'androidDeviceAllowlist'>;
9
+ type BindingOptions = {
10
+ env?: NodeJS.ProcessEnv;
11
+ policyOverrides?: BindingPolicyOverrides;
12
+ configuredPlatform?: CliFlags['platform'];
13
+ configuredSession?: string;
14
+ inheritedPlatform?: CliFlags['platform'];
15
+ };
16
+ export declare function applyDefaultPlatformBinding<T extends LockableFlags>(flags: T, options?: BindingOptions): T;
17
+ export declare function resolveBindingSettings(options: BindingOptions): BindingSettings;
18
+ export {};
@@ -0,0 +1,12 @@
1
+ import type { SnapshotNode } from './snapshot.ts';
2
+ type SnapshotDisplayLine = {
3
+ node: SnapshotNode;
4
+ depth: number;
5
+ type: string;
6
+ text: string;
7
+ };
8
+ export declare function buildSnapshotDisplayLines(nodes: SnapshotNode[]): SnapshotDisplayLine[];
9
+ export declare function formatSnapshotLine(node: SnapshotNode, depth: number, hiddenGroup: boolean, normalizedType?: string): string;
10
+ export declare function displayLabel(node: SnapshotNode, type: string): string;
11
+ export declare function formatRole(type: string): string;
12
+ export {};
@@ -0,0 +1,42 @@
1
+ export type Rect = {
2
+ x: number;
3
+ y: number;
4
+ width: number;
5
+ height: number;
6
+ };
7
+ export type SnapshotOptions = {
8
+ interactiveOnly?: boolean;
9
+ compact?: boolean;
10
+ depth?: number;
11
+ scope?: string;
12
+ raw?: boolean;
13
+ };
14
+ export type RawSnapshotNode = {
15
+ index: number;
16
+ type?: string;
17
+ label?: string;
18
+ value?: string;
19
+ identifier?: string;
20
+ rect?: Rect;
21
+ enabled?: boolean;
22
+ selected?: boolean;
23
+ hittable?: boolean;
24
+ depth?: number;
25
+ parentIndex?: number;
26
+ };
27
+ export type SnapshotNode = RawSnapshotNode & {
28
+ ref: string;
29
+ };
30
+ export type SnapshotState = {
31
+ nodes: SnapshotNode[];
32
+ createdAt: number;
33
+ truncated?: boolean;
34
+ backend?: 'xctest' | 'android';
35
+ };
36
+ export declare function attachRefs(nodes: RawSnapshotNode[]): SnapshotNode[];
37
+ export declare function normalizeRef(input: string): string | null;
38
+ export declare function findNodeByRef(nodes: SnapshotNode[], ref: string): SnapshotNode | null;
39
+ export declare function centerOfRect(rect: Rect): {
40
+ x: number;
41
+ y: number;
42
+ };
@@ -0,0 +1,3 @@
1
+ export declare function resolveTimeoutMs(raw: string | undefined, fallback: number, min: number): number;
2
+ /** Alias for `resolveTimeoutMs` — semantically marks the caller expects seconds. */
3
+ export declare const resolveTimeoutSeconds: typeof resolveTimeoutMs;
@@ -0,0 +1,2 @@
1
+ export declare function readVersion(): string;
2
+ export declare function findProjectRoot(): string;
package/package.json CHANGED
@@ -1,10 +1,18 @@
1
1
  {
2
2
  "name": "agent-device",
3
- "version": "0.7.21",
3
+ "version": "0.8.0",
4
4
  "description": "Unified control plane for physical and virtual devices via an agent-driven CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Callstack",
7
7
  "type": "module",
8
+ "main": "dist/src/index.js",
9
+ "types": "dist/src/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/src/index.js",
13
+ "types": "./dist/src/index.d.ts"
14
+ }
15
+ },
8
16
  "engines": {
9
17
  "node": ">=22"
10
18
  },
@@ -35,6 +35,8 @@ Use this skill as a router, not a full manual.
35
35
  - Android local QA: use `install` or `reinstall` for `.apk`/`.aab` files, then relaunch by installed package name.
36
36
  - Android React Native + Metro flows: set runtime hints with `runtime set` before `open <package> --relaunch`.
37
37
  - In mixed-device environments, always pin the exact target with `--serial`, `--device`, `--udid`, or an isolation scope.
38
+ - For session-bound automation runs, prefer a pre-bound session/platform instead of repeating selectors on every command: set `AGENT_DEVICE_SESSION`, set `AGENT_DEVICE_PLATFORM`, and the daemon will enforce the shared lock policy across CLI, typed client, and RPC entry points.
39
+ - Use `--session-lock reject|strip` (or `AGENT_DEVICE_SESSION_LOCK`) only when you need to override the default reject behavior. Lock mode applies to nested `batch` steps too.
38
40
 
39
41
  ## Canonical Flows
40
42
 
@@ -73,6 +75,35 @@ agent-device close
73
75
 
74
76
  Do not use `open <apk|aab> --relaunch` on Android. Install/reinstall binaries first, then relaunch by package.
75
77
 
78
+ ### 1c) Session-Bound Automation Flow
79
+
80
+ ```bash
81
+ export AGENT_DEVICE_SESSION=qa-ios
82
+ export AGENT_DEVICE_PLATFORM=ios
83
+ export AGENT_DEVICE_SESSION_LOCK=strip
84
+
85
+ agent-device open MyApp --relaunch
86
+ agent-device snapshot -i
87
+ agent-device batch --steps-file /tmp/qa-steps.json --json
88
+ agent-device close
89
+ ```
90
+
91
+ Use this for orchestrators that must preserve one bound session/device across many plain CLI calls without a wrapper script. In `strip` mode, conflicting selectors such as `--target`, `--device`, `--udid`, `--serial`, and isolation-scope overrides are ignored instead of retargeting the run.
92
+
93
+ ### 1d) Android Emulator Session-Bound Flow
94
+
95
+ ```bash
96
+ export AGENT_DEVICE_SESSION=qa-android
97
+ export AGENT_DEVICE_PLATFORM=android
98
+
99
+ agent-device reinstall MyApp /path/to/app-debug.apk --serial emulator-5554
100
+ agent-device --session-lock reject open com.example.myapp --relaunch
101
+ agent-device snapshot -i
102
+ agent-device close --shutdown
103
+ ```
104
+
105
+ Use this when an Android emulator session must stay pinned while an agent or test runner issues plain CLI commands over time.
106
+
76
107
  ### 2) Debug/Crash Flow
77
108
 
78
109
  ```bash
@@ -147,17 +178,22 @@ agent-device session list
147
178
  ```
148
179
 
149
180
  Use `boot` only as fallback when `open` cannot find/connect to a ready target.
181
+ If the workspace repeats the same selectors or device/session flags, prefer a checked-in `agent-device.json` or `--config <path>` over repeating them inline.
182
+ Environment-level defaults follow the same fields via `AGENT_DEVICE_*` names, so persistent host-specific values belong there rather than in committed project config.
183
+ That includes bound-session defaults such as `sessionLock` / `AGENT_DEVICE_SESSION_LOCK` when automation should consistently reject or strip conflicting device routing flags.
150
184
  For Android emulators by AVD name, use `boot --platform android --device <avd-name>`.
151
185
  For Android emulators without GUI, add `--headless`.
152
186
  Use `--target mobile|tv` with `--platform` (required) to pick phone/tablet vs TV targets (AndroidTV/tvOS).
153
187
  For Android React Native + Metro flows, install or reinstall the APK first, set runtime hints with `runtime set`, then use `open <package> --relaunch`; do not use `open <apk|aab> --relaunch`.
154
188
  For local iOS QA in mixed simulator/device environments, use `ensure-simulator` and pass `--device` or `--udid` so automation does not attach to a physical device by accident.
189
+ For session-bound automation, prefer `AGENT_DEVICE_SESSION` + `AGENT_DEVICE_PLATFORM`; that bound-session default now enables lock mode automatically.
155
190
 
156
191
  Isolation scoping quick reference:
157
192
  - `--ios-simulator-device-set <path>` scopes iOS simulator discovery + command execution to one simulator set.
158
193
  - `--android-device-allowlist <serials>` scopes Android discovery/selection to comma/space separated serials.
159
194
  - Scope is applied before selectors (`--device`, `--udid`, `--serial`); out-of-scope selectors fail with `DEVICE_NOT_FOUND`.
160
195
  - With iOS simulator-set scope enabled, iOS physical devices are not enumerated.
196
+ - In bound-session `strip` mode, conflicting per-call scope/selectors are ignored and the configured binding is restored for the request. Batch steps still inherit the parent `--platform` when they do not set their own.
161
197
 
162
198
  Simulator provisioning quick reference:
163
199
  - Use `ensure-simulator` to create or reuse a named iOS simulator inside a device set before starting a session.
@@ -50,6 +50,17 @@ curl -sS "${AGENT_DEVICE_DAEMON_BASE_URL}/rpc" \
50
50
  -d '{"jsonrpc":"2.0","id":"rel-1","method":"agent_device.lease.release","params":{"leaseId":"<lease-id>"}}'
51
51
  ```
52
52
 
53
+ Example session-locked command request:
54
+
55
+ ```bash
56
+ curl -sS "${AGENT_DEVICE_DAEMON_BASE_URL}/rpc" \
57
+ -H "content-type: application/json" \
58
+ -H "Authorization: Bearer <token>" \
59
+ -d '{"jsonrpc":"2.0","id":"cmd-1","method":"agent_device.command","params":{"session":"qa-ios","command":"snapshot","positionals":[],"meta":{"lockPolicy":"reject","lockPlatform":"ios","tenantId":"acme","runId":"run-123","leaseId":"<lease-id>"}}}'
60
+ ```
61
+
62
+ Direct RPC callers can send the same session lock concept as the CLI and typed client through `meta.lockPolicy` and optional `meta.lockPlatform`.
63
+
53
64
  ## Command admission contract
54
65
 
55
66
  For tenant-isolated command execution, pass all four flags:
@@ -14,6 +14,7 @@ Sessions isolate device context. A device can only be held by one session at a t
14
14
  - Name sessions semantically.
15
15
  - Close sessions when done.
16
16
  - Use separate sessions for parallel work.
17
+ - For orchestrated QA runs, prefer a pre-bound session/platform over repeating per-command selectors.
17
18
  - For remote tenant-scoped automation, run commands with:
18
19
  `--tenant <id> --session-isolation tenant --run-id <id> --lease-id <id>`
19
20
  - In iOS sessions, use `open <app>`. `open <url>` opens deep links; on devices `http(s)://` opens Safari when no app is active, and custom schemes require an active app in the session.
@@ -21,11 +22,46 @@ Sessions isolate device context. A device can only be held by one session at a t
21
22
  - On iOS, `appstate` is session-scoped and requires a matching active session on the target device.
22
23
  - For dev loops where runtime state can persist (for example React Native Fast Refresh), use `open <app> --relaunch` to restart the app process in the same session.
23
24
  - Use `--save-script [path]` to record replay scripts on `close`; path is a file path and parent directories are created automatically.
24
- - Use `close --shutdown` (iOS simulator only) to shut down the simulator as part of session teardown, preventing resource leakage in multi-tenant or CI workloads.
25
+ - Use `close --shutdown` on iOS simulators or Android emulators to shut down the target as part of session teardown, preventing resource leakage in multi-tenant or CI workloads.
25
26
  - For ambiguous bare `--save-script` values, prefer `--save-script=workflow.ad` or `./workflow.ad`.
26
27
  - For deterministic replay scripts, prefer selector-based actions and assertions.
27
28
  - Use `replay -u` to update selector drift during maintenance.
28
29
 
30
+ ## Session-bound automation
31
+
32
+ Use this when an external orchestrator must keep every CLI call on the same session/device without a wrapper script.
33
+
34
+ ```bash
35
+ export AGENT_DEVICE_SESSION=qa-ios
36
+ export AGENT_DEVICE_PLATFORM=ios
37
+ export AGENT_DEVICE_SESSION_LOCK=strip
38
+
39
+ agent-device open MyApp --relaunch
40
+ agent-device snapshot -i
41
+ agent-device press @e3
42
+ agent-device close
43
+ ```
44
+
45
+ - `AGENT_DEVICE_SESSION` and `AGENT_DEVICE_PLATFORM` provide the default binding when `--session` and `--platform` are omitted.
46
+ - A configured `AGENT_DEVICE_SESSION` enables lock policy enforcement by convention. The default mode is `reject`.
47
+ - `--session-lock reject|strip` or `AGENT_DEVICE_SESSION_LOCK=reject|strip` controls whether conflicting selectors fail or are ignored.
48
+ - The daemon enforces the same lock policy for CLI requests, typed client calls, and direct RPC commands.
49
+ - Conflicts include explicit retargeting selectors such as `--platform`, `--target`, `--device`, `--udid`, `--serial`, `--ios-simulator-device-set`, and `--android-device-allowlist`.
50
+ - `--session-locked`, `--session-lock-conflicts`, `AGENT_DEVICE_SESSION_LOCKED`, and `AGENT_DEVICE_SESSION_LOCK_CONFLICTS` remain supported as compatibility aliases.
51
+ - Lock policy applies to nested `batch` steps too. If a step omits `platform`, it still inherits the parent batch `--platform` instead of being silently replaced by an environment default.
52
+
53
+ Android emulator variant:
54
+
55
+ ```bash
56
+ export AGENT_DEVICE_SESSION=qa-android
57
+ export AGENT_DEVICE_PLATFORM=android
58
+
59
+ agent-device reinstall MyApp /path/to/app-debug.apk --serial emulator-5554
60
+ agent-device --session-lock reject open com.example.myapp --relaunch
61
+ agent-device snapshot -i
62
+ agent-device close --shutdown
63
+ ```
64
+
29
65
  ## Scoped device isolation
30
66
 
31
67
  Use scoped discovery when sessions must not see host-global device lists.