agent-device 0.4.0 → 0.4.2
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 +20 -12
- package/dist/src/797.js +1 -0
- package/dist/src/bin.js +40 -29
- package/dist/src/daemon.js +21 -17
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +8 -2
- package/package.json +2 -2
- package/skills/agent-device/SKILL.md +23 -14
- package/skills/agent-device/references/permissions.md +7 -2
- package/skills/agent-device/references/session-management.md +5 -1
- package/src/__tests__/cli-close.test.ts +155 -0
- package/src/__tests__/cli-help.test.ts +102 -0
- package/src/cli.ts +68 -22
- package/src/core/__tests__/capabilities.test.ts +2 -1
- package/src/core/__tests__/dispatch-open.test.ts +25 -0
- package/src/core/__tests__/open-target.test.ts +40 -1
- package/src/core/capabilities.ts +1 -1
- package/src/core/dispatch.ts +22 -0
- package/src/core/open-target.ts +14 -0
- package/src/daemon/__tests__/device-ready.test.ts +52 -0
- package/src/daemon/__tests__/session-store.test.ts +23 -0
- package/src/daemon/device-ready.ts +146 -4
- package/src/daemon/handlers/__tests__/session.test.ts +477 -0
- package/src/daemon/handlers/session.ts +198 -93
- package/src/daemon/handlers/snapshot.ts +210 -185
- package/src/daemon/session-store.ts +16 -6
- package/src/daemon/types.ts +2 -1
- package/src/daemon-client.ts +138 -17
- package/src/daemon.ts +99 -9
- package/src/platforms/android/__tests__/index.test.ts +118 -1
- package/src/platforms/android/index.ts +77 -47
- package/src/platforms/ios/__tests__/index.test.ts +292 -4
- package/src/platforms/ios/__tests__/runner-client.test.ts +42 -0
- package/src/platforms/ios/apps.ts +358 -0
- package/src/platforms/ios/config.ts +28 -0
- package/src/platforms/ios/devicectl.ts +134 -0
- package/src/platforms/ios/devices.ts +15 -2
- package/src/platforms/ios/index.ts +20 -455
- package/src/platforms/ios/runner-client.ts +171 -69
- package/src/platforms/ios/simulator.ts +164 -0
- package/src/utils/__tests__/args.test.ts +66 -2
- package/src/utils/__tests__/daemon-client.test.ts +95 -0
- package/src/utils/__tests__/keyed-lock.test.ts +55 -0
- package/src/utils/__tests__/process-identity.test.ts +33 -0
- package/src/utils/args.ts +37 -1
- package/src/utils/command-schema.ts +58 -27
- package/src/utils/interactors.ts +2 -2
- package/src/utils/keyed-lock.ts +14 -0
- package/src/utils/process-identity.ts +100 -0
- package/src/utils/timeouts.ts +9 -0
- package/dist/src/274.js +0 -1
- package/src/daemon/__tests__/app-state.test.ts +0 -138
- package/src/daemon/app-state.ts +0 -65
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { dispatchCommand, resolveTargetDevice } from '../../core/dispatch.ts';
|
|
3
3
|
import { isCommandSupportedOnDevice } from '../../core/capabilities.ts';
|
|
4
|
-
import { isDeepLinkTarget } from '../../core/open-target.ts';
|
|
4
|
+
import { isDeepLinkTarget, resolveIosDeviceDeepLinkBundleId } from '../../core/open-target.ts';
|
|
5
5
|
import { AppError, asAppError } from '../../utils/errors.ts';
|
|
6
6
|
import type { DeviceInfo } from '../../utils/device.ts';
|
|
7
7
|
import type { DaemonRequest, DaemonResponse, SessionAction, SessionState } from '../types.ts';
|
|
8
8
|
import { SessionStore } from '../session-store.ts';
|
|
9
9
|
import { contextFromFlags } from '../context.ts';
|
|
10
10
|
import { ensureDeviceReady } from '../device-ready.ts';
|
|
11
|
-
import { resolveIosAppStateFromSnapshots } from '../app-state.ts';
|
|
12
11
|
import { stopIosRunnerSession } from '../../platforms/ios/runner-client.ts';
|
|
13
12
|
import { attachRefs, type RawSnapshotNode, type SnapshotState } from '../../utils/snapshot.ts';
|
|
14
13
|
import { extractNodeText, normalizeType, pruneGroupNodes } from '../snapshot-processing.ts';
|
|
@@ -26,17 +25,61 @@ type ReinstallOps = {
|
|
|
26
25
|
android: (device: DeviceInfo, app: string, appPath: string) => Promise<{ package: string }>;
|
|
27
26
|
};
|
|
28
27
|
|
|
28
|
+
const IOS_APPSTATE_SESSION_REQUIRED_MESSAGE =
|
|
29
|
+
'iOS appstate requires an active session on the target device. Run open first (for example: open --session sim --platform ios --device "<name>" <app>).';
|
|
30
|
+
|
|
31
|
+
function requireSessionOrExplicitSelector(
|
|
32
|
+
command: string,
|
|
33
|
+
session: SessionState | undefined,
|
|
34
|
+
flags: DaemonRequest['flags'] | undefined,
|
|
35
|
+
): DaemonResponse | null {
|
|
36
|
+
if (session || hasExplicitDeviceSelector(flags)) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
error: {
|
|
42
|
+
code: 'INVALID_ARGS',
|
|
43
|
+
message: `${command} requires an active session or an explicit device selector (e.g. --platform ios).`,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
29
48
|
function hasExplicitDeviceSelector(flags: DaemonRequest['flags'] | undefined): boolean {
|
|
30
49
|
return Boolean(flags?.platform || flags?.device || flags?.udid || flags?.serial);
|
|
31
50
|
}
|
|
32
51
|
|
|
52
|
+
function hasExplicitSessionFlag(flags: DaemonRequest['flags'] | undefined): boolean {
|
|
53
|
+
return typeof flags?.session === 'string' && flags.session.trim().length > 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function selectorTargetsSessionDevice(
|
|
57
|
+
flags: DaemonRequest['flags'] | undefined,
|
|
58
|
+
session: SessionState | undefined,
|
|
59
|
+
): boolean {
|
|
60
|
+
if (!session) return false;
|
|
61
|
+
if (!hasExplicitDeviceSelector(flags)) return true;
|
|
62
|
+
if (flags?.platform && flags.platform !== session.device.platform) return false;
|
|
63
|
+
if (flags?.udid && flags.udid !== session.device.id) return false;
|
|
64
|
+
if (flags?.serial && flags.serial !== session.device.id) return false;
|
|
65
|
+
if (flags?.device) {
|
|
66
|
+
return flags.device.trim().toLowerCase() === session.device.name.trim().toLowerCase();
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
33
71
|
async function resolveCommandDevice(params: {
|
|
34
72
|
session: SessionState | undefined;
|
|
35
73
|
flags: DaemonRequest['flags'] | undefined;
|
|
36
74
|
ensureReadyFn: typeof ensureDeviceReady;
|
|
75
|
+
resolveTargetDeviceFn: typeof resolveTargetDevice;
|
|
37
76
|
ensureReady?: boolean;
|
|
38
77
|
}): Promise<DeviceInfo> {
|
|
39
|
-
const
|
|
78
|
+
const shouldUseExplicitSelector = hasExplicitDeviceSelector(params.flags);
|
|
79
|
+
const device =
|
|
80
|
+
shouldUseExplicitSelector || !params.session
|
|
81
|
+
? await params.resolveTargetDeviceFn(params.flags ?? {})
|
|
82
|
+
: params.session.device;
|
|
40
83
|
if (params.ensureReady !== false) {
|
|
41
84
|
await params.ensureReadyFn(device);
|
|
42
85
|
}
|
|
@@ -54,10 +97,22 @@ const defaultReinstallOps: ReinstallOps = {
|
|
|
54
97
|
},
|
|
55
98
|
};
|
|
56
99
|
|
|
57
|
-
async function resolveIosBundleIdForOpen(
|
|
58
|
-
|
|
100
|
+
async function resolveIosBundleIdForOpen(
|
|
101
|
+
device: DeviceInfo,
|
|
102
|
+
openTarget: string | undefined,
|
|
103
|
+
currentAppBundleId?: string,
|
|
104
|
+
): Promise<string | undefined> {
|
|
105
|
+
if (device.platform !== 'ios' || !openTarget) return undefined;
|
|
106
|
+
if (isDeepLinkTarget(openTarget)) {
|
|
107
|
+
if (device.kind === 'device') {
|
|
108
|
+
return resolveIosDeviceDeepLinkBundleId(currentAppBundleId, openTarget);
|
|
109
|
+
}
|
|
59
110
|
return undefined;
|
|
60
111
|
}
|
|
112
|
+
return await tryResolveIosAppBundleId(device, openTarget);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function tryResolveIosAppBundleId(device: DeviceInfo, openTarget: string): Promise<string | undefined> {
|
|
61
116
|
try {
|
|
62
117
|
const { resolveIosApp } = await import('../../platforms/ios/index.ts');
|
|
63
118
|
return await resolveIosApp(device, openTarget);
|
|
@@ -66,6 +121,92 @@ async function resolveIosBundleIdForOpen(device: DeviceInfo, openTarget: string
|
|
|
66
121
|
}
|
|
67
122
|
}
|
|
68
123
|
|
|
124
|
+
async function handleAppStateCommand(params: {
|
|
125
|
+
req: DaemonRequest;
|
|
126
|
+
sessionName: string;
|
|
127
|
+
sessionStore: SessionStore;
|
|
128
|
+
ensureReady: typeof ensureDeviceReady;
|
|
129
|
+
resolveDevice: typeof resolveTargetDevice;
|
|
130
|
+
}): Promise<DaemonResponse> {
|
|
131
|
+
const { req, sessionName, sessionStore, ensureReady, resolveDevice } = params;
|
|
132
|
+
const session = sessionStore.get(sessionName);
|
|
133
|
+
const flags = req.flags ?? {};
|
|
134
|
+
if (!session && hasExplicitSessionFlag(flags)) {
|
|
135
|
+
const iOSSessionHint =
|
|
136
|
+
flags.platform === 'ios'
|
|
137
|
+
? `No active session "${sessionName}". Run open with --session ${sessionName} first.`
|
|
138
|
+
: `No active session "${sessionName}". Run open with --session ${sessionName} first, or omit --session to query by device selector.`;
|
|
139
|
+
return {
|
|
140
|
+
ok: false,
|
|
141
|
+
error: {
|
|
142
|
+
code: 'SESSION_NOT_FOUND',
|
|
143
|
+
message: iOSSessionHint,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const guard = requireSessionOrExplicitSelector('appstate', session, flags);
|
|
148
|
+
if (guard) return guard;
|
|
149
|
+
|
|
150
|
+
const shouldUseSessionStateForIos = session?.device.platform === 'ios' && selectorTargetsSessionDevice(flags, session);
|
|
151
|
+
const targetsIos = flags.platform === 'ios';
|
|
152
|
+
if (targetsIos && !shouldUseSessionStateForIos) {
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
error: {
|
|
156
|
+
code: 'SESSION_NOT_FOUND',
|
|
157
|
+
message: IOS_APPSTATE_SESSION_REQUIRED_MESSAGE,
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (shouldUseSessionStateForIos) {
|
|
162
|
+
const appName = session.appName ?? session.appBundleId;
|
|
163
|
+
if (!session.appName && !session.appBundleId) {
|
|
164
|
+
return {
|
|
165
|
+
ok: false,
|
|
166
|
+
error: {
|
|
167
|
+
code: 'COMMAND_FAILED',
|
|
168
|
+
message: 'No foreground app is tracked for this iOS session. Open an app in the session, then retry appstate.',
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
ok: true,
|
|
174
|
+
data: {
|
|
175
|
+
platform: 'ios',
|
|
176
|
+
appName: appName ?? 'unknown',
|
|
177
|
+
appBundleId: session.appBundleId,
|
|
178
|
+
source: 'session',
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const device = await resolveCommandDevice({
|
|
183
|
+
session,
|
|
184
|
+
flags,
|
|
185
|
+
ensureReadyFn: ensureReady,
|
|
186
|
+
resolveTargetDeviceFn: resolveDevice,
|
|
187
|
+
ensureReady: true,
|
|
188
|
+
});
|
|
189
|
+
if (device.platform === 'ios') {
|
|
190
|
+
return {
|
|
191
|
+
ok: false,
|
|
192
|
+
error: {
|
|
193
|
+
code: 'SESSION_NOT_FOUND',
|
|
194
|
+
message: IOS_APPSTATE_SESSION_REQUIRED_MESSAGE,
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const { getAndroidAppState } = await import('../../platforms/android/index.ts');
|
|
199
|
+
const state = await getAndroidAppState(device);
|
|
200
|
+
return {
|
|
201
|
+
ok: true,
|
|
202
|
+
data: {
|
|
203
|
+
platform: 'android',
|
|
204
|
+
package: state.package,
|
|
205
|
+
activity: state.activity,
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
69
210
|
export async function handleSessionCommands(params: {
|
|
70
211
|
req: DaemonRequest;
|
|
71
212
|
sessionName: string;
|
|
@@ -74,6 +215,7 @@ export async function handleSessionCommands(params: {
|
|
|
74
215
|
invoke: (req: DaemonRequest) => Promise<DaemonResponse>;
|
|
75
216
|
dispatch?: typeof dispatchCommand;
|
|
76
217
|
ensureReady?: typeof ensureDeviceReady;
|
|
218
|
+
resolveTargetDevice?: typeof resolveTargetDevice;
|
|
77
219
|
reinstallOps?: ReinstallOps;
|
|
78
220
|
}): Promise<DaemonResponse | null> {
|
|
79
221
|
const {
|
|
@@ -84,10 +226,12 @@ export async function handleSessionCommands(params: {
|
|
|
84
226
|
invoke,
|
|
85
227
|
dispatch: dispatchOverride,
|
|
86
228
|
ensureReady: ensureReadyOverride,
|
|
229
|
+
resolveTargetDevice: resolveTargetDeviceOverride,
|
|
87
230
|
reinstallOps = defaultReinstallOps,
|
|
88
231
|
} = params;
|
|
89
232
|
const dispatch = dispatchOverride ?? dispatchCommand;
|
|
90
233
|
const ensureReady = ensureReadyOverride ?? ensureDeviceReady;
|
|
234
|
+
const resolveDevice = resolveTargetDeviceOverride ?? resolveTargetDevice;
|
|
91
235
|
const command = req.command;
|
|
92
236
|
|
|
93
237
|
if (command === 'session_list') {
|
|
@@ -136,56 +280,50 @@ export async function handleSessionCommands(params: {
|
|
|
136
280
|
if (command === 'apps') {
|
|
137
281
|
const session = sessionStore.get(sessionName);
|
|
138
282
|
const flags = req.flags ?? {};
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
const device = await resolveCommandDevice({ session, flags, ensureReadyFn: ensureReady, ensureReady: true });
|
|
283
|
+
const guard = requireSessionOrExplicitSelector(command, session, flags);
|
|
284
|
+
if (guard) return guard;
|
|
285
|
+
const device = await resolveCommandDevice({
|
|
286
|
+
session,
|
|
287
|
+
flags,
|
|
288
|
+
ensureReadyFn: ensureReady,
|
|
289
|
+
resolveTargetDeviceFn: resolveDevice,
|
|
290
|
+
ensureReady: true,
|
|
291
|
+
});
|
|
149
292
|
if (!isCommandSupportedOnDevice('apps', device)) {
|
|
150
293
|
return { ok: false, error: { code: 'UNSUPPORTED_OPERATION', message: 'apps is not supported on this device' } };
|
|
151
294
|
}
|
|
295
|
+
const appsFilter = req.flags?.appsFilter ?? 'all';
|
|
152
296
|
if (device.platform === 'ios') {
|
|
153
|
-
const {
|
|
154
|
-
const apps = await
|
|
155
|
-
if (req.flags?.appsMetadata) {
|
|
156
|
-
return { ok: true, data: { apps } };
|
|
157
|
-
}
|
|
297
|
+
const { listIosApps } = await import('../../platforms/ios/index.ts');
|
|
298
|
+
const apps = await listIosApps(device, appsFilter);
|
|
158
299
|
const formatted = apps.map((app) =>
|
|
159
300
|
app.name && app.name !== app.bundleId ? `${app.name} (${app.bundleId})` : app.bundleId,
|
|
160
301
|
);
|
|
161
302
|
return { ok: true, data: { apps: formatted } };
|
|
162
303
|
}
|
|
163
|
-
const { listAndroidApps
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return { ok: true, data: { apps } };
|
|
304
|
+
const { listAndroidApps } = await import('../../platforms/android/index.ts');
|
|
305
|
+
const apps = await listAndroidApps(device, appsFilter);
|
|
306
|
+
const formatted = apps.map((app) =>
|
|
307
|
+
app.name && app.name !== app.package ? `${app.name} (${app.package})` : app.package,
|
|
308
|
+
);
|
|
309
|
+
return { ok: true, data: { apps: formatted } };
|
|
170
310
|
}
|
|
171
311
|
|
|
172
312
|
if (command === 'boot') {
|
|
173
313
|
const session = sessionStore.get(sessionName);
|
|
174
314
|
const flags = req.flags ?? {};
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
const device = session?.device ?? (await resolveTargetDevice(flags));
|
|
315
|
+
const guard = requireSessionOrExplicitSelector(command, session, flags);
|
|
316
|
+
if (guard) return guard;
|
|
317
|
+
const device = await resolveCommandDevice({
|
|
318
|
+
session,
|
|
319
|
+
flags,
|
|
320
|
+
ensureReadyFn: ensureReady,
|
|
321
|
+
resolveTargetDeviceFn: resolveDevice,
|
|
322
|
+
ensureReady: true,
|
|
323
|
+
});
|
|
185
324
|
if (!isCommandSupportedOnDevice('boot', device)) {
|
|
186
325
|
return { ok: false, error: { code: 'UNSUPPORTED_OPERATION', message: 'boot is not supported on this device' } };
|
|
187
326
|
}
|
|
188
|
-
await ensureReady(device);
|
|
189
327
|
return {
|
|
190
328
|
ok: true,
|
|
191
329
|
data: {
|
|
@@ -199,61 +337,20 @@ export async function handleSessionCommands(params: {
|
|
|
199
337
|
}
|
|
200
338
|
|
|
201
339
|
if (command === 'appstate') {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
data: {
|
|
210
|
-
platform: 'ios',
|
|
211
|
-
appBundleId: session.appBundleId,
|
|
212
|
-
appName: session.appName ?? session.appBundleId,
|
|
213
|
-
source: 'session',
|
|
214
|
-
},
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
const snapshotResult = await resolveIosAppStateFromSnapshots(
|
|
218
|
-
device,
|
|
219
|
-
logPath,
|
|
220
|
-
session?.trace?.outPath,
|
|
221
|
-
req.flags,
|
|
222
|
-
);
|
|
223
|
-
return {
|
|
224
|
-
ok: true,
|
|
225
|
-
data: {
|
|
226
|
-
platform: 'ios',
|
|
227
|
-
appName: snapshotResult.appName,
|
|
228
|
-
appBundleId: snapshotResult.appBundleId,
|
|
229
|
-
source: snapshotResult.source,
|
|
230
|
-
},
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
const { getAndroidAppState } = await import('../../platforms/android/index.ts');
|
|
234
|
-
const state = await getAndroidAppState(device);
|
|
235
|
-
return {
|
|
236
|
-
ok: true,
|
|
237
|
-
data: {
|
|
238
|
-
platform: 'android',
|
|
239
|
-
package: state.package,
|
|
240
|
-
activity: state.activity,
|
|
241
|
-
},
|
|
242
|
-
};
|
|
340
|
+
return await handleAppStateCommand({
|
|
341
|
+
req,
|
|
342
|
+
sessionName,
|
|
343
|
+
sessionStore,
|
|
344
|
+
ensureReady,
|
|
345
|
+
resolveDevice,
|
|
346
|
+
});
|
|
243
347
|
}
|
|
244
348
|
|
|
245
349
|
if (command === 'reinstall') {
|
|
246
350
|
const session = sessionStore.get(sessionName);
|
|
247
351
|
const flags = req.flags ?? {};
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
ok: false,
|
|
251
|
-
error: {
|
|
252
|
-
code: 'INVALID_ARGS',
|
|
253
|
-
message: 'reinstall requires an active session or an explicit device selector (e.g. --platform ios).',
|
|
254
|
-
},
|
|
255
|
-
};
|
|
256
|
-
}
|
|
352
|
+
const guard = requireSessionOrExplicitSelector(command, session, flags);
|
|
353
|
+
if (guard) return guard;
|
|
257
354
|
const app = req.positionals?.[0]?.trim();
|
|
258
355
|
const appPathInput = req.positionals?.[1]?.trim();
|
|
259
356
|
if (!app || !appPathInput) {
|
|
@@ -269,7 +366,13 @@ export async function handleSessionCommands(params: {
|
|
|
269
366
|
error: { code: 'INVALID_ARGS', message: `App binary not found: ${appPath}` },
|
|
270
367
|
};
|
|
271
368
|
}
|
|
272
|
-
const device = await resolveCommandDevice({
|
|
369
|
+
const device = await resolveCommandDevice({
|
|
370
|
+
session,
|
|
371
|
+
flags,
|
|
372
|
+
ensureReadyFn: ensureReady,
|
|
373
|
+
resolveTargetDeviceFn: resolveDevice,
|
|
374
|
+
ensureReady: false,
|
|
375
|
+
});
|
|
273
376
|
if (!isCommandSupportedOnDevice('reinstall', device)) {
|
|
274
377
|
return {
|
|
275
378
|
ok: false,
|
|
@@ -331,7 +434,8 @@ export async function handleSessionCommands(params: {
|
|
|
331
434
|
},
|
|
332
435
|
};
|
|
333
436
|
}
|
|
334
|
-
|
|
437
|
+
await ensureReady(session.device);
|
|
438
|
+
const appBundleId = await resolveIosBundleIdForOpen(session.device, openTarget, session.appBundleId);
|
|
335
439
|
const openPositionals = requestedOpenTarget ? (req.positionals ?? []) : [openTarget];
|
|
336
440
|
if (shouldRelaunch) {
|
|
337
441
|
const closeTarget = appBundleId ?? openTarget;
|
|
@@ -346,7 +450,7 @@ export async function handleSessionCommands(params: {
|
|
|
346
450
|
...session,
|
|
347
451
|
appBundleId,
|
|
348
452
|
appName: openTarget,
|
|
349
|
-
recordSession: session.recordSession || req.flags?.saveScript
|
|
453
|
+
recordSession: session.recordSession || Boolean(req.flags?.saveScript),
|
|
350
454
|
snapshot: undefined,
|
|
351
455
|
};
|
|
352
456
|
sessionStore.recordAction(nextSession, {
|
|
@@ -377,7 +481,7 @@ export async function handleSessionCommands(params: {
|
|
|
377
481
|
},
|
|
378
482
|
};
|
|
379
483
|
}
|
|
380
|
-
const device = await
|
|
484
|
+
const device = await resolveDevice(req.flags ?? {});
|
|
381
485
|
const inUse = sessionStore.toArray().find((s) => s.device.id === device.id);
|
|
382
486
|
if (inUse) {
|
|
383
487
|
return {
|
|
@@ -389,6 +493,7 @@ export async function handleSessionCommands(params: {
|
|
|
389
493
|
},
|
|
390
494
|
};
|
|
391
495
|
}
|
|
496
|
+
await ensureReady(device);
|
|
392
497
|
const appBundleId = await resolveIosBundleIdForOpen(device, openTarget);
|
|
393
498
|
if (shouldRelaunch && openTarget) {
|
|
394
499
|
const closeTarget = appBundleId ?? openTarget;
|
|
@@ -405,7 +510,7 @@ export async function handleSessionCommands(params: {
|
|
|
405
510
|
createdAt: Date.now(),
|
|
406
511
|
appBundleId,
|
|
407
512
|
appName: openTarget,
|
|
408
|
-
recordSession: req.flags?.saveScript
|
|
513
|
+
recordSession: Boolean(req.flags?.saveScript),
|
|
409
514
|
actions: [],
|
|
410
515
|
};
|
|
411
516
|
sessionStore.recordAction(session, {
|