agent-device 0.4.2 → 0.5.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 (91) hide show
  1. package/README.md +2 -9
  2. package/dist/src/797.js +1 -1
  3. package/dist/src/bin.js +5 -5
  4. package/dist/src/daemon.js +16 -20
  5. package/package.json +7 -9
  6. package/skills/agent-device/SKILL.md +3 -6
  7. package/skills/agent-device/references/permissions.md +3 -15
  8. package/skills/agent-device/references/snapshot-refs.md +1 -4
  9. package/dist/bin/axsnapshot +0 -0
  10. package/ios-runner/AXSnapshot/Package.swift +0 -18
  11. package/ios-runner/AXSnapshot/Sources/AXSnapshot/main.swift +0 -444
  12. package/src/__tests__/cli-close.test.ts +0 -155
  13. package/src/__tests__/cli-help.test.ts +0 -102
  14. package/src/bin.ts +0 -3
  15. package/src/cli.ts +0 -305
  16. package/src/core/__tests__/capabilities.test.ts +0 -75
  17. package/src/core/__tests__/dispatch-open.test.ts +0 -25
  18. package/src/core/__tests__/open-target.test.ts +0 -55
  19. package/src/core/capabilities.ts +0 -57
  20. package/src/core/dispatch.ts +0 -382
  21. package/src/core/open-target.ts +0 -27
  22. package/src/daemon/__tests__/device-ready.test.ts +0 -52
  23. package/src/daemon/__tests__/is-predicates.test.ts +0 -68
  24. package/src/daemon/__tests__/selectors.test.ts +0 -261
  25. package/src/daemon/__tests__/session-routing.test.ts +0 -108
  26. package/src/daemon/__tests__/session-selector.test.ts +0 -64
  27. package/src/daemon/__tests__/session-store.test.ts +0 -142
  28. package/src/daemon/__tests__/snapshot-processing.test.ts +0 -47
  29. package/src/daemon/action-utils.ts +0 -29
  30. package/src/daemon/context.ts +0 -48
  31. package/src/daemon/device-ready.ts +0 -155
  32. package/src/daemon/handlers/__tests__/find.test.ts +0 -99
  33. package/src/daemon/handlers/__tests__/interaction.test.ts +0 -22
  34. package/src/daemon/handlers/__tests__/replay-heal.test.ts +0 -509
  35. package/src/daemon/handlers/__tests__/session-reinstall.test.ts +0 -219
  36. package/src/daemon/handlers/__tests__/session.test.ts +0 -820
  37. package/src/daemon/handlers/__tests__/snapshot-handler.test.ts +0 -92
  38. package/src/daemon/handlers/__tests__/snapshot.test.ts +0 -128
  39. package/src/daemon/handlers/find.ts +0 -324
  40. package/src/daemon/handlers/interaction.ts +0 -550
  41. package/src/daemon/handlers/parse-utils.ts +0 -8
  42. package/src/daemon/handlers/record-trace.ts +0 -154
  43. package/src/daemon/handlers/session.ts +0 -1137
  44. package/src/daemon/handlers/snapshot.ts +0 -439
  45. package/src/daemon/is-predicates.ts +0 -46
  46. package/src/daemon/selectors.ts +0 -540
  47. package/src/daemon/session-routing.ts +0 -22
  48. package/src/daemon/session-selector.ts +0 -39
  49. package/src/daemon/session-store.ts +0 -296
  50. package/src/daemon/snapshot-processing.ts +0 -131
  51. package/src/daemon/types.ts +0 -56
  52. package/src/daemon-client.ts +0 -272
  53. package/src/daemon.ts +0 -295
  54. package/src/platforms/__tests__/boot-diagnostics.test.ts +0 -59
  55. package/src/platforms/android/__tests__/index.test.ts +0 -274
  56. package/src/platforms/android/devices.ts +0 -196
  57. package/src/platforms/android/index.ts +0 -784
  58. package/src/platforms/android/ui-hierarchy.ts +0 -312
  59. package/src/platforms/boot-diagnostics.ts +0 -128
  60. package/src/platforms/ios/__tests__/index.test.ts +0 -312
  61. package/src/platforms/ios/__tests__/runner-client.test.ts +0 -155
  62. package/src/platforms/ios/apps.ts +0 -358
  63. package/src/platforms/ios/ax-snapshot.ts +0 -207
  64. package/src/platforms/ios/config.ts +0 -28
  65. package/src/platforms/ios/devicectl.ts +0 -134
  66. package/src/platforms/ios/devices.ts +0 -100
  67. package/src/platforms/ios/index.ts +0 -20
  68. package/src/platforms/ios/runner-client.ts +0 -994
  69. package/src/platforms/ios/simulator.ts +0 -164
  70. package/src/utils/__tests__/args.test.ts +0 -239
  71. package/src/utils/__tests__/daemon-client.test.ts +0 -95
  72. package/src/utils/__tests__/exec.test.ts +0 -16
  73. package/src/utils/__tests__/finders.test.ts +0 -34
  74. package/src/utils/__tests__/keyed-lock.test.ts +0 -55
  75. package/src/utils/__tests__/process-identity.test.ts +0 -33
  76. package/src/utils/__tests__/retry.test.ts +0 -44
  77. package/src/utils/args.ts +0 -239
  78. package/src/utils/command-schema.ts +0 -622
  79. package/src/utils/device.ts +0 -84
  80. package/src/utils/errors.ts +0 -35
  81. package/src/utils/exec.ts +0 -339
  82. package/src/utils/finders.ts +0 -101
  83. package/src/utils/interactive.ts +0 -4
  84. package/src/utils/interactors.ts +0 -173
  85. package/src/utils/keyed-lock.ts +0 -14
  86. package/src/utils/output.ts +0 -204
  87. package/src/utils/process-identity.ts +0 -100
  88. package/src/utils/retry.ts +0 -180
  89. package/src/utils/snapshot.ts +0 -64
  90. package/src/utils/timeouts.ts +0 -9
  91. package/src/utils/version.ts +0 -26
@@ -1,155 +0,0 @@
1
- import type { DeviceInfo } from '../utils/device.ts';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import { promises as fs } from 'node:fs';
5
- import { runCmd } from '../utils/exec.ts';
6
- import { AppError } from '../utils/errors.ts';
7
- import { resolveTimeoutMs } from '../utils/timeouts.ts';
8
- import { resolveIosDevicectlHint, IOS_DEVICECTL_DEFAULT_HINT } from '../platforms/ios/devicectl.ts';
9
-
10
- const IOS_DEVICE_READY_TIMEOUT_MS = resolveTimeoutMs(
11
- process.env.AGENT_DEVICE_IOS_DEVICE_READY_TIMEOUT_MS,
12
- 15_000,
13
- 1_000,
14
- );
15
- const IOS_DEVICE_READY_COMMAND_TIMEOUT_BUFFER_MS = 3_000;
16
-
17
- export async function ensureDeviceReady(device: DeviceInfo): Promise<void> {
18
- if (device.platform === 'ios') {
19
- if (device.kind === 'simulator') {
20
- const { ensureBootedSimulator } = await import('../platforms/ios/index.ts');
21
- await ensureBootedSimulator(device);
22
- return;
23
- }
24
- if (device.kind === 'device') {
25
- await ensureIosDeviceReady(device.id);
26
- return;
27
- }
28
- }
29
- if (device.platform === 'android') {
30
- const { waitForAndroidBoot } = await import('../platforms/android/devices.ts');
31
- await waitForAndroidBoot(device.id);
32
- }
33
- }
34
-
35
- async function ensureIosDeviceReady(deviceId: string): Promise<void> {
36
- const jsonPath = path.join(
37
- os.tmpdir(),
38
- `agent-device-ready-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.json`,
39
- );
40
- const timeoutSeconds = Math.max(1, Math.ceil(IOS_DEVICE_READY_TIMEOUT_MS / 1000));
41
- try {
42
- const result = await runCmd(
43
- 'xcrun',
44
- [
45
- 'devicectl',
46
- 'device',
47
- 'info',
48
- 'details',
49
- '--device',
50
- deviceId,
51
- '--json-output',
52
- jsonPath,
53
- '--timeout',
54
- String(timeoutSeconds),
55
- ],
56
- {
57
- allowFailure: true,
58
- timeoutMs: IOS_DEVICE_READY_TIMEOUT_MS + IOS_DEVICE_READY_COMMAND_TIMEOUT_BUFFER_MS,
59
- },
60
- );
61
- const stdout = String(result.stdout ?? '');
62
- const stderr = String(result.stderr ?? '');
63
- const parsed = await readIosReadyPayload(jsonPath);
64
- if (result.exitCode === 0) {
65
- if (!parsed.parsed) {
66
- throw new AppError('COMMAND_FAILED', 'iOS device readiness probe failed', {
67
- kind: 'probe_inconclusive',
68
- deviceId,
69
- stdout,
70
- stderr,
71
- hint: 'CoreDevice returned success but readiness JSON output was missing or invalid. Retry; if it persists restart Xcode and the iOS device.',
72
- });
73
- }
74
- const tunnelState = parsed?.tunnelState?.toLowerCase();
75
- if (tunnelState === 'connecting') {
76
- throw new AppError('COMMAND_FAILED', 'iOS device is not ready for automation', {
77
- kind: 'not_ready',
78
- deviceId,
79
- tunnelState,
80
- hint: 'Device tunnel is still connecting. Keep the device unlocked and connected by cable until it is fully available in Xcode Devices, then retry.',
81
- });
82
- }
83
- return;
84
- }
85
- throw new AppError('COMMAND_FAILED', 'iOS device is not ready for automation', {
86
- kind: 'not_ready',
87
- deviceId,
88
- stdout,
89
- stderr,
90
- exitCode: result.exitCode,
91
- tunnelState: parsed?.tunnelState,
92
- hint: resolveIosReadyHint(stdout, stderr),
93
- });
94
- } catch (error) {
95
- if (error instanceof AppError && error.code === 'COMMAND_FAILED') {
96
- const kind = typeof error.details?.kind === 'string' ? error.details.kind : '';
97
- if (kind === 'not_ready') {
98
- throw error;
99
- }
100
- const details = (error.details ?? {}) as {
101
- stdout?: string;
102
- stderr?: string;
103
- timeoutMs?: number;
104
- };
105
- const stdout = String(details.stdout ?? '');
106
- const stderr = String(details.stderr ?? '');
107
- const timeoutMs = Number(details.timeoutMs ?? IOS_DEVICE_READY_TIMEOUT_MS);
108
- const timeoutHint = `CoreDevice did not respond within ${timeoutMs}ms. Keep the device unlocked and trusted, then retry; if it persists restart Xcode and the iOS device.`;
109
- throw new AppError('COMMAND_FAILED', 'iOS device readiness probe failed', {
110
- deviceId,
111
- cause: error.message,
112
- timeoutMs,
113
- stdout,
114
- stderr,
115
- hint: stdout || stderr ? resolveIosReadyHint(stdout, stderr) : timeoutHint,
116
- }, error);
117
- }
118
- throw new AppError('COMMAND_FAILED', 'iOS device readiness probe failed', {
119
- deviceId,
120
- hint: 'Reconnect the device, keep it unlocked, and retry.',
121
- }, error instanceof Error ? error : undefined);
122
- } finally {
123
- await fs.rm(jsonPath, { force: true }).catch(() => {});
124
- }
125
- }
126
-
127
- export function parseIosReadyPayload(payload: unknown): { tunnelState?: string } {
128
- const result = (payload as { result?: unknown } | null | undefined)?.result;
129
- if (!result || typeof result !== 'object') return {};
130
- const direct = (result as { connectionProperties?: { tunnelState?: unknown } }).connectionProperties?.tunnelState;
131
- const nested = (result as { device?: { connectionProperties?: { tunnelState?: unknown } } }).device?.connectionProperties?.tunnelState;
132
- const tunnelState = typeof direct === 'string' ? direct : typeof nested === 'string' ? nested : undefined;
133
- return tunnelState ? { tunnelState } : {};
134
- }
135
-
136
- async function readIosReadyPayload(jsonPath: string): Promise<{ parsed: boolean; tunnelState?: string }> {
137
- try {
138
- const payloadText = await fs.readFile(jsonPath, 'utf8');
139
- const payload = JSON.parse(payloadText) as unknown;
140
- const parsed = parseIosReadyPayload(payload);
141
- return { parsed: true, tunnelState: parsed.tunnelState };
142
- } catch {
143
- return { parsed: false };
144
- }
145
- }
146
-
147
- export function resolveIosReadyHint(stdout: string, stderr: string): string {
148
- const devicectlHint = resolveIosDevicectlHint(stdout, stderr);
149
- if (devicectlHint) return devicectlHint;
150
- const text = `${stdout}\n${stderr}`.toLowerCase();
151
- if (text.includes('timed out waiting for all destinations')) {
152
- return 'Xcode destination did not become available in time. Keep device unlocked and retry.';
153
- }
154
- return IOS_DEVICECTL_DEFAULT_HINT;
155
- }
@@ -1,99 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { parseFindArgs } from '../find.ts';
4
- import { AppError } from '../../../utils/errors.ts';
5
-
6
- test('parseFindArgs defaults to click with any locator', () => {
7
- const parsed = parseFindArgs(['Login']);
8
- assert.equal(parsed.locator, 'any');
9
- assert.equal(parsed.query, 'Login');
10
- assert.equal(parsed.action, 'click');
11
- });
12
-
13
- test('parseFindArgs supports explicit locator and fill payload', () => {
14
- const parsed = parseFindArgs(['label', 'Email', 'fill', 'user@example.com']);
15
- assert.equal(parsed.locator, 'label');
16
- assert.equal(parsed.query, 'Email');
17
- assert.equal(parsed.action, 'fill');
18
- assert.equal(parsed.value, 'user@example.com');
19
- });
20
-
21
- test('parseFindArgs parses wait timeout', () => {
22
- const parsed = parseFindArgs(['text', 'Settings', 'wait', '2500']);
23
- assert.equal(parsed.locator, 'text');
24
- assert.equal(parsed.action, 'wait');
25
- assert.equal(parsed.timeoutMs, 2500);
26
- });
27
-
28
- test('parseFindArgs parses get text', () => {
29
- const parsed = parseFindArgs(['label', 'Price', 'get', 'text']);
30
- assert.equal(parsed.locator, 'label');
31
- assert.equal(parsed.query, 'Price');
32
- assert.equal(parsed.action, 'get_text');
33
- });
34
-
35
- test('parseFindArgs parses get attrs', () => {
36
- const parsed = parseFindArgs(['id', 'btn-1', 'get', 'attrs']);
37
- assert.equal(parsed.locator, 'id');
38
- assert.equal(parsed.query, 'btn-1');
39
- assert.equal(parsed.action, 'get_attrs');
40
- });
41
-
42
- test('parseFindArgs rejects invalid get sub-action', () => {
43
- assert.throws(
44
- () => parseFindArgs(['text', 'Settings', 'get', 'foo']),
45
- (err: unknown) =>
46
- err instanceof AppError &&
47
- err.code === 'INVALID_ARGS' &&
48
- err.message.includes('find get only supports text or attrs'),
49
- );
50
- });
51
-
52
- test('parseFindArgs parses type action with value', () => {
53
- const parsed = parseFindArgs(['label', 'Name', 'type', 'Jane']);
54
- assert.equal(parsed.locator, 'label');
55
- assert.equal(parsed.query, 'Name');
56
- assert.equal(parsed.action, 'type');
57
- assert.equal(parsed.value, 'Jane');
58
- });
59
-
60
- test('parseFindArgs joins multi-word fill value', () => {
61
- const parsed = parseFindArgs(['label', 'Bio', 'fill', 'hello', 'world']);
62
- assert.equal(parsed.action, 'fill');
63
- assert.equal(parsed.value, 'hello world');
64
- });
65
-
66
- test('parseFindArgs joins multi-word type value', () => {
67
- const parsed = parseFindArgs(['label', 'Bio', 'type', 'hello', 'world']);
68
- assert.equal(parsed.action, 'type');
69
- assert.equal(parsed.value, 'hello world');
70
- });
71
-
72
- test('parseFindArgs wait without timeout leaves timeoutMs undefined', () => {
73
- const parsed = parseFindArgs(['text', 'Loading', 'wait']);
74
- assert.equal(parsed.action, 'wait');
75
- assert.equal(parsed.timeoutMs, undefined);
76
- });
77
-
78
- test('parseFindArgs wait with non-numeric timeout leaves timeoutMs undefined', () => {
79
- const parsed = parseFindArgs(['text', 'Loading', 'wait', 'abc']);
80
- assert.equal(parsed.action, 'wait');
81
- assert.equal(parsed.timeoutMs, undefined);
82
- });
83
-
84
- test('parseFindArgs throws on unsupported action', () => {
85
- assert.throws(
86
- () => parseFindArgs(['text', 'OK', 'swipe']),
87
- (err: unknown) =>
88
- err instanceof AppError &&
89
- err.code === 'INVALID_ARGS' &&
90
- err.message.includes('Unsupported find action: swipe'),
91
- );
92
- });
93
-
94
- test('parseFindArgs with bare locator yields empty query', () => {
95
- const parsed = parseFindArgs(['text']);
96
- assert.equal(parsed.locator, 'text');
97
- assert.equal(parsed.query, '');
98
- assert.equal(parsed.action, 'click');
99
- });
@@ -1,22 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { unsupportedRefSnapshotFlags } from '../interaction.ts';
4
-
5
- test('unsupportedRefSnapshotFlags returns unsupported snapshot flags for @ref flows', () => {
6
- const unsupported = unsupportedRefSnapshotFlags({
7
- snapshotDepth: 2,
8
- snapshotScope: 'Login',
9
- snapshotRaw: true,
10
- snapshotBackend: 'ax',
11
- });
12
- assert.deepEqual(unsupported, ['--depth', '--scope', '--raw', '--backend']);
13
- });
14
-
15
- test('unsupportedRefSnapshotFlags returns empty when no ref-unsupported flags are present', () => {
16
- const unsupported = unsupportedRefSnapshotFlags({
17
- platform: 'ios',
18
- session: 'default',
19
- verbose: true,
20
- });
21
- assert.deepEqual(unsupported, []);
22
- });