Skip to content

Commit 8e75778

Browse files
committed
refactor: address review follow-ups
- Add DEVICE_IN_USE to the batch error taxonomy in exploration.md (now observable by clients after earlier fix, needs agent-facing guidance) - Delete one-line src/platforms/linux/index.ts barrel; both consumers (core/dispatch, daemon/handlers/snapshot-capture) now import from platforms/linux/snapshot directly - Replace inline { tenantId; runId; leaseId } shapes with MetroBridgeScope alias at client-types, metro, and cli/commands/connection-runtime - Consolidate remaining inline setTimeout wrappers onto utils/timeouts.ts#sleep (12 files, ~18 sites). Left test files and the runtime-clock aware helper in commands/selector-read-utils alone. Also removes the local sleepMs helper from daemon-client.ts.
1 parent 29a34a8 commit 8e75778

18 files changed

Lines changed: 42 additions & 47 deletions

skills/agent-device/references/exploration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ Common batch error categories:
338338
- `SESSION_NOT_FOUND`: open or select the correct session, then retry.
339339
- `UNSUPPORTED_OPERATION`: switch to a supported command or surface.
340340
- `AMBIGUOUS_MATCH`: refine the selector or locator, then retry the failed step.
341+
- `DEVICE_IN_USE`: the device is held by another session — close or reuse the existing session before retrying.
341342
- `COMMAND_FAILED`: add sync guards and retry from the failing step.
342343

343344
## Stop conditions

src/cli/commands/connection-runtime.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { resolveDaemonPaths } from '../../daemon/config.ts';
22
import { stopMetroTunnel } from '../../metro.ts';
3+
import type { MetroBridgeScope } from '../../client-metro-companion-contract.ts';
34
import {
45
readRemoteConnectionState,
56
writeRemoteConnectionState,
@@ -145,11 +146,7 @@ export async function prepareConnectedMetro(
145146
client: AgentDeviceClient,
146147
remoteConfigPath: string,
147148
session: string,
148-
bridgeScope: {
149-
tenantId: string;
150-
runId: string;
151-
leaseId: string;
152-
},
149+
bridgeScope: MetroBridgeScope,
153150
): Promise<{
154151
runtime?: SessionRuntimeHints;
155152
cleanup?: NonNullable<RemoteConnectionState['metro']>;

src/client-metro.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import fs from 'node:fs';
22
import path from 'node:path';
3+
import { sleep } from './utils/timeouts.ts';
34
import { ensureMetroCompanion } from './client-metro-companion.ts';
45
import type { MetroBridgeScope } from './client-metro-companion-contract.ts';
56
import type {
@@ -215,7 +216,7 @@ function installDependenciesIfNeeded(
215216
}
216217

217218
async function wait(ms: number): Promise<void> {
218-
await new Promise((resolve) => setTimeout(resolve, ms));
219+
await sleep(ms);
219220
}
220221

221222
async function fetchText(

src/client-types.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type { DeviceKind, DeviceTarget, Platform, PlatformSelector } from './uti
1111
import type { FindLocator } from './utils/finders.ts';
1212
import type { ScreenshotOverlayRef, SnapshotNode, SnapshotVisibility } from './utils/snapshot.ts';
1313
import type { MetroPrepareKind, PrepareMetroRuntimeResult } from './client-metro.ts';
14+
import type { MetroBridgeScope } from './client-metro-companion-contract.ts';
1415

1516
export type { FindLocator } from './utils/finders.ts';
1617

@@ -270,11 +271,7 @@ export type MetroPrepareOptions = {
270271
publicBaseUrl: string;
271272
proxyBaseUrl?: string;
272273
bearerToken?: string;
273-
bridgeScope?: {
274-
tenantId: string;
275-
runId: string;
276-
leaseId: string;
277-
};
274+
bridgeScope?: MetroBridgeScope;
278275
launchUrl?: string;
279276
companionProfileKey?: string;
280277
companionConsumerKey?: string;

src/core/dispatch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { getInteractor, type Interactor, type RunnerContext } from './interactor
1313
import { runIosRunnerCommand } from '../platforms/ios/runner-client.ts';
1414
import { runMacOsPressAction, runMacOsReadTextAction } from '../platforms/ios/macos-helper.ts';
1515
import { pushIosNotification } from '../platforms/ios/index.ts';
16-
import { snapshotLinux } from '../platforms/linux/index.ts';
16+
import { snapshotLinux } from '../platforms/linux/snapshot.ts';
1717
import { rightClickLinux, middleClickLinux } from '../platforms/linux/input-actions.ts';
1818
import type { SessionSurface } from './session-surface.ts';
1919
import { isDeepLinkTarget } from './open-target.ts';

src/daemon-client.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import http from 'node:http';
33
import https from 'node:https';
44
import fs from 'node:fs';
55
import path from 'node:path';
6+
import { sleep } from './utils/timeouts.ts';
67
import { AppError, toAppErrorCode } from './utils/errors.ts';
78
import type {
89
DaemonArtifact,
@@ -483,7 +484,7 @@ async function ensureDaemon(settings: DaemonClientSettings): Promise<DaemonInfo>
483484

484485
// Detached daemon startup can race on busy CI hosts; retry when no metadata exists yet.
485486
if (!metadataState.hasInfo && !metadataState.hasLock) {
486-
await sleepMs(150);
487+
await sleep(150);
487488
continue;
488489
}
489490
}
@@ -509,15 +510,11 @@ async function waitForDaemonInfo(
509510
while (Date.now() - start < timeoutMs) {
510511
const info = readDaemonInfo(settings.paths.infoPath);
511512
if (info && (await canConnect(info, settings.transportPreference))) return info;
512-
await new Promise((resolve) => setTimeout(resolve, 100));
513+
await sleep(100);
513514
}
514515
return null;
515516
}
516517

517-
async function sleepMs(ms: number): Promise<void> {
518-
await new Promise((resolve) => setTimeout(resolve, ms));
519-
}
520-
521518
async function recoverDaemonLockHolder(paths: DaemonPaths): Promise<boolean> {
522519
const state = getDaemonMetadataState(paths);
523520
if (!state.hasLock || state.hasInfo) return false;

src/daemon/handlers/find.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { dispatchCommand, resolveTargetDevice } from '../../core/dispatch.ts';
2+
import { sleep } from '../../utils/timeouts.ts';
23
import { findBestMatchesByLocator, parseFindArgs, type FindLocator } from '../../utils/finders.ts';
34
import { centerOfRect, type SnapshotState } from '../../utils/snapshot.ts';
45
import type { DaemonRequest, DaemonResponse } from '../types.ts';
@@ -200,7 +201,7 @@ async function handleFindWait(
200201
}
201202
return { ok: true, data: { found: true, waitedMs: Date.now() - start } };
202203
}
203-
await new Promise((resolve) => setTimeout(resolve, 300));
204+
await sleep(300);
204205
}
205206
return errorResponse('COMMAND_FAILED', 'find wait timed out');
206207
}

src/daemon/handlers/record-trace-android.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import fs from 'node:fs';
22
import { emitDiagnostic } from '../../utils/diagnostics.ts';
3+
import { sleep } from '../../utils/timeouts.ts';
34
import type { DaemonResponse, SessionState } from '../types.ts';
45
import { formatRecordTraceExecFailure } from '../record-trace-errors.ts';
56
import type { RecordTraceDeps } from './record-trace-types.ts';
@@ -67,7 +68,7 @@ async function waitForAndroidProcessExit(
6768
if (!(await isAndroidProcessRunning(deps, deviceId, pid))) {
6869
return true;
6970
}
70-
await new Promise((resolve) => setTimeout(resolve, ANDROID_PROCESS_EXIT_POLL_MS));
71+
await sleep(ANDROID_PROCESS_EXIT_POLL_MS);
7172
}
7273
return !(await isAndroidProcessRunning(deps, deviceId, pid));
7374
}
@@ -96,7 +97,7 @@ async function waitForAndroidRemoteFileStability(
9697
stableCount = 0;
9798
}
9899
previousSize = currentSize;
99-
await new Promise((resolve) => setTimeout(resolve, ANDROID_REMOTE_FILE_POLL_MS));
100+
await sleep(ANDROID_REMOTE_FILE_POLL_MS);
100101
}
101102
}
102103

@@ -128,7 +129,7 @@ async function waitForAndroidRecordingReady(
128129
return true;
129130
}
130131

131-
await new Promise((resolve) => setTimeout(resolve, ANDROID_REMOTE_FILE_POLL_MS));
132+
await sleep(ANDROID_REMOTE_FILE_POLL_MS);
132133
}
133134

134135
return false;
@@ -196,7 +197,7 @@ async function copyAndroidRecordingWithValidation(params: {
196197
}
197198

198199
if (attempt < ANDROID_LOCAL_VIDEO_ATTEMPTS - 1) {
199-
await new Promise((resolve) => setTimeout(resolve, ANDROID_LOCAL_VIDEO_RETRY_DELAY_MS));
200+
await sleep(ANDROID_LOCAL_VIDEO_RETRY_DELAY_MS);
200201
}
201202
}
202203

src/daemon/handlers/record-trace-recording.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import fs from 'node:fs';
22
import path from 'node:path';
3+
import { sleep } from '../../utils/timeouts.ts';
34
import { resolveTargetDevice, type CommandFlags } from '../../core/dispatch.ts';
45
import { isCommandSupportedOnDevice } from '../../core/capabilities.ts';
56
import { ensureDeviceReady } from '../device-ready.ts';
@@ -81,7 +82,7 @@ async function waitForLocalRecordingSettleWindow(outPath: string): Promise<numbe
8182
return Date.now();
8283
}
8384

84-
await new Promise((resolve) => setTimeout(resolve, LOCAL_RECORDING_READY_POLL_MS));
85+
await sleep(LOCAL_RECORDING_READY_POLL_MS);
8586
}
8687

8788
return Date.now();

src/daemon/handlers/snapshot-alert.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { isCommandSupportedOnDevice } from '../../core/capabilities.ts';
2+
import { sleep } from '../../utils/timeouts.ts';
23
import { runIosRunnerCommand } from '../../platforms/ios/runner-client.ts';
34
import { runMacOsAlertAction } from '../../platforms/ios/macos-helper.ts';
45
import { AppError } from '../../utils/errors.ts';
@@ -54,7 +55,7 @@ export async function handleAlertCommand(
5455
} catch {
5556
// keep waiting
5657
}
57-
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
58+
await sleep(POLL_INTERVAL_MS);
5859
}
5960
return errorResponse('COMMAND_FAILED', 'alert wait timed out');
6061
}
@@ -73,7 +74,7 @@ export async function handleAlertCommand(
7374
const msg = String((err as { message?: unknown })?.message ?? '').toLowerCase();
7475
if (!msg.includes('alert not found') && !msg.includes('no alert')) break;
7576
}
76-
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
77+
await sleep(POLL_INTERVAL_MS);
7778
}
7879
throw withAlertFallbackHint(lastError);
7980
}
@@ -101,7 +102,7 @@ export async function handleAlertCommand(
101102
} catch {
102103
// keep waiting
103104
}
104-
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
105+
await sleep(POLL_INTERVAL_MS);
105106
}
106107
return errorResponse('COMMAND_FAILED', 'alert wait timed out');
107108
}
@@ -132,7 +133,7 @@ export async function handleAlertCommand(
132133
const msg = String((err as { message?: unknown })?.message ?? '').toLowerCase();
133134
if (!msg.includes('alert not found') && !msg.includes('no alert')) break;
134135
}
135-
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
136+
await sleep(POLL_INTERVAL_MS);
136137
}
137138
// lastError is always set because ALERT_ACTION_RETRY_MS > 0
138139
throw withAlertFallbackHint(lastError);

0 commit comments

Comments
 (0)