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.
- package/README.md +2 -9
- package/dist/src/797.js +1 -1
- package/dist/src/bin.js +5 -5
- package/dist/src/daemon.js +16 -20
- package/package.json +7 -9
- package/skills/agent-device/SKILL.md +3 -6
- package/skills/agent-device/references/permissions.md +3 -15
- package/skills/agent-device/references/snapshot-refs.md +1 -4
- package/dist/bin/axsnapshot +0 -0
- package/ios-runner/AXSnapshot/Package.swift +0 -18
- package/ios-runner/AXSnapshot/Sources/AXSnapshot/main.swift +0 -444
- package/src/__tests__/cli-close.test.ts +0 -155
- package/src/__tests__/cli-help.test.ts +0 -102
- package/src/bin.ts +0 -3
- package/src/cli.ts +0 -305
- package/src/core/__tests__/capabilities.test.ts +0 -75
- package/src/core/__tests__/dispatch-open.test.ts +0 -25
- package/src/core/__tests__/open-target.test.ts +0 -55
- package/src/core/capabilities.ts +0 -57
- package/src/core/dispatch.ts +0 -382
- package/src/core/open-target.ts +0 -27
- package/src/daemon/__tests__/device-ready.test.ts +0 -52
- package/src/daemon/__tests__/is-predicates.test.ts +0 -68
- package/src/daemon/__tests__/selectors.test.ts +0 -261
- package/src/daemon/__tests__/session-routing.test.ts +0 -108
- package/src/daemon/__tests__/session-selector.test.ts +0 -64
- package/src/daemon/__tests__/session-store.test.ts +0 -142
- package/src/daemon/__tests__/snapshot-processing.test.ts +0 -47
- package/src/daemon/action-utils.ts +0 -29
- package/src/daemon/context.ts +0 -48
- package/src/daemon/device-ready.ts +0 -155
- package/src/daemon/handlers/__tests__/find.test.ts +0 -99
- package/src/daemon/handlers/__tests__/interaction.test.ts +0 -22
- package/src/daemon/handlers/__tests__/replay-heal.test.ts +0 -509
- package/src/daemon/handlers/__tests__/session-reinstall.test.ts +0 -219
- package/src/daemon/handlers/__tests__/session.test.ts +0 -820
- package/src/daemon/handlers/__tests__/snapshot-handler.test.ts +0 -92
- package/src/daemon/handlers/__tests__/snapshot.test.ts +0 -128
- package/src/daemon/handlers/find.ts +0 -324
- package/src/daemon/handlers/interaction.ts +0 -550
- package/src/daemon/handlers/parse-utils.ts +0 -8
- package/src/daemon/handlers/record-trace.ts +0 -154
- package/src/daemon/handlers/session.ts +0 -1137
- package/src/daemon/handlers/snapshot.ts +0 -439
- package/src/daemon/is-predicates.ts +0 -46
- package/src/daemon/selectors.ts +0 -540
- package/src/daemon/session-routing.ts +0 -22
- package/src/daemon/session-selector.ts +0 -39
- package/src/daemon/session-store.ts +0 -296
- package/src/daemon/snapshot-processing.ts +0 -131
- package/src/daemon/types.ts +0 -56
- package/src/daemon-client.ts +0 -272
- package/src/daemon.ts +0 -295
- package/src/platforms/__tests__/boot-diagnostics.test.ts +0 -59
- package/src/platforms/android/__tests__/index.test.ts +0 -274
- package/src/platforms/android/devices.ts +0 -196
- package/src/platforms/android/index.ts +0 -784
- package/src/platforms/android/ui-hierarchy.ts +0 -312
- package/src/platforms/boot-diagnostics.ts +0 -128
- package/src/platforms/ios/__tests__/index.test.ts +0 -312
- package/src/platforms/ios/__tests__/runner-client.test.ts +0 -155
- package/src/platforms/ios/apps.ts +0 -358
- package/src/platforms/ios/ax-snapshot.ts +0 -207
- package/src/platforms/ios/config.ts +0 -28
- package/src/platforms/ios/devicectl.ts +0 -134
- package/src/platforms/ios/devices.ts +0 -100
- package/src/platforms/ios/index.ts +0 -20
- package/src/platforms/ios/runner-client.ts +0 -994
- package/src/platforms/ios/simulator.ts +0 -164
- package/src/utils/__tests__/args.test.ts +0 -239
- package/src/utils/__tests__/daemon-client.test.ts +0 -95
- package/src/utils/__tests__/exec.test.ts +0 -16
- package/src/utils/__tests__/finders.test.ts +0 -34
- package/src/utils/__tests__/keyed-lock.test.ts +0 -55
- package/src/utils/__tests__/process-identity.test.ts +0 -33
- package/src/utils/__tests__/retry.test.ts +0 -44
- package/src/utils/args.ts +0 -239
- package/src/utils/command-schema.ts +0 -622
- package/src/utils/device.ts +0 -84
- package/src/utils/errors.ts +0 -35
- package/src/utils/exec.ts +0 -339
- package/src/utils/finders.ts +0 -101
- package/src/utils/interactive.ts +0 -4
- package/src/utils/interactors.ts +0 -173
- package/src/utils/keyed-lock.ts +0 -14
- package/src/utils/output.ts +0 -204
- package/src/utils/process-identity.ts +0 -100
- package/src/utils/retry.ts +0 -180
- package/src/utils/snapshot.ts +0 -64
- package/src/utils/timeouts.ts +0 -9
- 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
|
-
});
|