From c27b3f8a9a463f14c780bd35def00fe3c51451be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Fri, 25 Sep 2026 11:09:16 +0200 Subject: [PATCH 1/4] feat(sdk): export the client's option, result, and node types from the package root createAgentDeviceClient returned AgentDeviceClient, whose option and result types are all named in client-types.ts, but the root exported none of them. A consumer typed a client parameter as ReturnType, a result as Awaited>, and the nodes a snapshot returns by hand. The root now re-exports every client type, the kernel snapshot types a result is made of (SnapshotNode, RawSnapshotNode, SnapshotState, Rect, Point), and the error types normalizeAgentDeviceError answers in. Types only: the JavaScript entry is unchanged. The client example takes AgentDeviceClient and returns AgentDeviceDevice. --- examples/sdk/client-session.ts | 6 +++++- src/sdk/index.ts | 21 +++++++++++++++++++++ website/docs/docs/client-api.md | 5 +++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/examples/sdk/client-session.ts b/examples/sdk/client-session.ts index 7bc4ffd4f2..48721a7e92 100644 --- a/examples/sdk/client-session.ts +++ b/examples/sdk/client-session.ts @@ -13,13 +13,17 @@ * Run: node --experimental-strip-types examples/sdk/client-session.ts */ import { + type AgentDeviceClient, + type AgentDeviceDevice, AppError, createAgentDeviceClient, isAgentDeviceError, normalizeAgentDeviceError, } from 'agent-device'; -async function resolveSnapshotCapableIosDevice(client: ReturnType) { +async function resolveSnapshotCapableIosDevice( + client: AgentDeviceClient, +): Promise { const devices = await client.devices.list({ platform: 'ios' }); const device = devices[0]; if (!device) { diff --git a/src/sdk/index.ts b/src/sdk/index.ts index b8689a6125..ab8d7338d7 100644 --- a/src/sdk/index.ts +++ b/src/sdk/index.ts @@ -6,3 +6,24 @@ export { normalizeAgentDeviceError, } from '@agent-device/kernel/errors'; export { centerOfRect } from '@agent-device/kernel/snapshot'; + +// The types the root's two functions are written in. `createAgentDeviceClient` returns +// `AgentDeviceClient`, whose every option and result type is a named type of +// `client-types.ts`; until now a consumer reached them only through +// `ReturnType` and `Awaited>`, and typed the +// nodes a snapshot returns by hand. +export type * from '../client/client-types.ts'; +export type { + AppErrorCode, + AppErrorDetails, + ErrorCause, + KnownAppErrorCode, + NormalizedError, +} from '@agent-device/kernel/errors'; +export type { + Point, + RawSnapshotNode, + Rect, + SnapshotNode, + SnapshotState, +} from '@agent-device/kernel/snapshot'; diff --git a/website/docs/docs/client-api.md b/website/docs/docs/client-api.md index d28f0d4009..7f7e6692ef 100644 --- a/website/docs/docs/client-api.md +++ b/website/docs/docs/client-api.md @@ -34,6 +34,11 @@ Supported public entry points for Node consumers: - `createLocalArtifactAdapter(options?)` - `AppError`, `isAgentDeviceError(error)`, `normalizeAgentDeviceError(error)` - `centerOfRect(rect)` + - types: `AgentDeviceClient`, `AgentDeviceClientConfig`, and every option and result type of + its methods (`AppOpenOptions`, `AppOpenResult`, `CaptureSnapshotOptions`, + `CaptureSnapshotResult`, `CaptureScreenshotResult`, `PressOptions`, `AgentDeviceDevice`, ...); + `SnapshotNode`, `RawSnapshotNode`, `SnapshotState`, `Rect`, `Point`; `AppErrorCode`, + `KnownAppErrorCode`, `AppErrorDetails`, `ErrorCause`, `NormalizedError` - `agent-device/io` - `createLocalArtifactAdapter(options?)` - types: `ArtifactAdapter`, `ArtifactDescriptor`, `CreateTempFileOptions`, `FileInputRef`, From 746d916ea392b7694138aebbf958362eb08e00df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Fri, 25 Sep 2026 11:28:34 +0200 Subject: [PATCH 2/4] docs(client-api): list the root types where the example drift guard reads them The client-api drift guard reads an example's imports one statement at a time and strips a leading 'type ' only right after the brace, and it reads the doc's manifest one nested bullet line at a time. The example now imports the root types through an import type statement, and the manifest names each type on a line that starts with a bullet, AgentDeviceDevice included, instead of on the continuation lines of one bullet. --- examples/sdk/client-session.ts | 3 +-- website/docs/docs/client-api.md | 11 ++++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/sdk/client-session.ts b/examples/sdk/client-session.ts index 48721a7e92..539386ecd4 100644 --- a/examples/sdk/client-session.ts +++ b/examples/sdk/client-session.ts @@ -13,13 +13,12 @@ * Run: node --experimental-strip-types examples/sdk/client-session.ts */ import { - type AgentDeviceClient, - type AgentDeviceDevice, AppError, createAgentDeviceClient, isAgentDeviceError, normalizeAgentDeviceError, } from 'agent-device'; +import type { AgentDeviceClient, AgentDeviceDevice } from 'agent-device'; async function resolveSnapshotCapableIosDevice( client: AgentDeviceClient, diff --git a/website/docs/docs/client-api.md b/website/docs/docs/client-api.md index 7f7e6692ef..2bcbc00342 100644 --- a/website/docs/docs/client-api.md +++ b/website/docs/docs/client-api.md @@ -34,11 +34,12 @@ Supported public entry points for Node consumers: - `createLocalArtifactAdapter(options?)` - `AppError`, `isAgentDeviceError(error)`, `normalizeAgentDeviceError(error)` - `centerOfRect(rect)` - - types: `AgentDeviceClient`, `AgentDeviceClientConfig`, and every option and result type of - its methods (`AppOpenOptions`, `AppOpenResult`, `CaptureSnapshotOptions`, - `CaptureSnapshotResult`, `CaptureScreenshotResult`, `PressOptions`, `AgentDeviceDevice`, ...); - `SnapshotNode`, `RawSnapshotNode`, `SnapshotState`, `Rect`, `Point`; `AppErrorCode`, - `KnownAppErrorCode`, `AppErrorDetails`, `ErrorCause`, `NormalizedError` + - types: `AgentDeviceClient`, `AgentDeviceClientConfig`, `AgentDeviceDevice` + - types: every option and result type of the client's methods, among them `AppOpenOptions`, + `AppOpenResult`, `CaptureSnapshotOptions`, `CaptureSnapshotResult`, `CaptureScreenshotResult`, + `PressOptions` + - types: `SnapshotNode`, `RawSnapshotNode`, `SnapshotState`, `Rect`, `Point` + - types: `NormalizedError`, `AppErrorCode`, `KnownAppErrorCode`, `AppErrorDetails`, `ErrorCause` - `agent-device/io` - `createLocalArtifactAdapter(options?)` - types: `ArtifactAdapter`, `ArtifactDescriptor`, `CreateTempFileOptions`, `FileInputRef`, From e13a949920ef219aee2487b67ca53f285834608f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Fri, 25 Sep 2026 12:11:22 +0200 Subject: [PATCH 3/4] feat(sdk): export the two observability results the client signatures import client-types.ts imports AgentArtifactsResult and CloudProviderSessionResult for its method signatures without re-exporting them, so the root wildcard could not reach them and the doc's 'every option and result type' was wider than the export. The root names them explicitly. --- src/sdk/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sdk/index.ts b/src/sdk/index.ts index ab8d7338d7..7556c9c42b 100644 --- a/src/sdk/index.ts +++ b/src/sdk/index.ts @@ -13,6 +13,11 @@ export { centerOfRect } from '@agent-device/kernel/snapshot'; // `ReturnType` and `Awaited>`, and typed the // nodes a snapshot returns by hand. export type * from '../client/client-types.ts'; +// Two results `client-types.ts` imports for its signatures without re-exporting them. +export type { + AgentArtifactsResult, + CloudProviderSessionResult, +} from '@agent-device/contracts/observability'; export type { AppErrorCode, AppErrorDetails, From 6c164081437eb93c28d5b61a7de162726ff76653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Fri, 25 Sep 2026 12:53:20 +0200 Subject: [PATCH 4/4] fix(sdk): keep the internal request envelope out of the package root MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The root's `export type *` chain reached `@agent-device/contracts/client`, which carried `InternalRequestOptions` and `CommandExecutionOptions`. Move both to `request-envelope.ts` under the command façade; the client façade keeps only `CommandRequestResult`. Co-Authored-By: Claude Opus 5.5 --- packages/contracts/src/client-request.ts | 96 +------------------- packages/contracts/src/facades/client.ts | 6 +- packages/contracts/src/facades/command.ts | 1 + packages/contracts/src/request-envelope.ts | 95 +++++++++++++++++++ src/agent-device-client.ts | 3 +- src/client/client-normalizers.ts | 2 +- src/client/lease-client.ts | 7 +- src/commands/__tests__/command-flags.test.ts | 2 +- src/commands/cli-grammar/common.ts | 8 +- src/commands/cli-grammar/types.ts | 7 +- src/commands/command-flags.ts | 3 +- test/wire-compat/closure-policy.ts | 2 +- 12 files changed, 112 insertions(+), 120 deletions(-) create mode 100644 packages/contracts/src/request-envelope.ts diff --git a/packages/contracts/src/client-request.ts b/packages/contracts/src/client-request.ts index c9b3f4e83c..63cc2154e4 100644 --- a/packages/contracts/src/client-request.ts +++ b/packages/contracts/src/client-request.ts @@ -1,97 +1,3 @@ -// The public API vocabulary for the internal request envelope every client call is projected into. - -import type { AppsFilter } from './app-inventory.ts'; -import type { BackMode } from './back-mode.ts'; -import type { ClickButton } from './click-button.ts'; -import type { RecordingExportQuality } from './recording-export-quality.ts'; -import type { RecordingScope } from './recording-scope.ts'; -import type { ScreenshotRequestFlags } from './screenshot.ts'; -import type { SwipePattern } from './scroll-gesture.ts'; -import type { SessionSurface } from './session-surface.ts'; -import type { - DaemonInstallSource, - DaemonResponseData, - NetworkIncludeMode, - SessionRuntimeHints, -} from '@agent-device/kernel/contracts'; -import type { SnapshotCommandOptionFields } from '@agent-device/kernel/snapshot'; -import type { DaemonBatchStep } from './batch-step.ts'; -import type { ReplayRequestFields } from './replay-request-fields.ts'; -import type { AgentDeviceClientConfig, AgentDeviceSelectionOptions } from './client-connection.ts'; - -export type CommandExecutionOptions = Partial & - ReplayRequestFields & - SnapshotCommandOptionFields & { - positionals?: string[]; - kind?: string; - out?: string; - artifact?: string; - dsym?: string; - searchPath?: string; - count?: number; - fps?: number; - recordingScope?: RecordingScope; - quality?: RecordingExportQuality; - hideTouches?: boolean; - intervalMs?: number; - delayMs?: number; - durationMs?: number; - holdMs?: number; - jitterPx?: number; - pixels?: number; - /** Scroll: repeat passes until this selector is visible on screen. */ - until?: string; - doubleTap?: boolean; - verify?: boolean; - settle?: boolean; - settleQuietMs?: number; - clickButton?: ClickButton; - pauseMs?: number; - pattern?: SwipePattern; - headless?: boolean; - restart?: boolean; - replayBackend?: string; - findFirst?: boolean; - findLast?: boolean; - networkInclude?: NetworkIncludeMode; - batchOnError?: 'stop'; - batchMaxSteps?: number; - batchSteps?: DaemonBatchStep[]; - }; - -export type InternalRequestOptions = AgentDeviceClientConfig & - AgentDeviceSelectionOptions & - CommandExecutionOptions & { - runtime?: SessionRuntimeHints; - overlayRefs?: boolean; - surface?: SessionSurface; - activity?: string; - launchConsole?: string; - launchArgs?: string[]; - relaunch?: boolean; - shutdown?: boolean; - saveScript?: boolean | string; - /** #1258: overwrite an existing --save-script target instead of refusing. Alias: --overwrite. */ - force?: boolean; - testIme?: boolean; - noRecord?: boolean; - /** Fill-only script parameter name used to publish `${VAR}` instead of literal text. */ - recordAs?: string; - /** #1271 stage 2: force-record this action; mutually exclusive with `noRecord`. */ - record?: boolean; - backMode?: BackMode; - metroHost?: string; - metroPort?: number; - bundleUrl?: string; - launchUrl?: string; - appsFilter?: AppsFilter; - installSource?: DaemonInstallSource; - retainMaterializedPaths?: boolean; - materializedPathRetentionMs?: number; - materializationId?: string; - leaseTtlMs?: number; - provider?: string; - providerSessionId?: string; - }; +import type { DaemonResponseData } from '@agent-device/kernel/contracts'; export type CommandRequestResult = DaemonResponseData; diff --git a/packages/contracts/src/facades/client.ts b/packages/contracts/src/facades/client.ts index 196eec8c94..8cd5d9dad2 100644 --- a/packages/contracts/src/facades/client.ts +++ b/packages/contracts/src/facades/client.ts @@ -89,11 +89,7 @@ export type { ReplayRunOptions, ReplayTestOptions, } from '../client-replay.ts'; -export type { - CommandExecutionOptions, - CommandRequestResult, - InternalRequestOptions, -} from '../client-request.ts'; +export type { CommandRequestResult } from '../client-request.ts'; export type { FindBaseOptions, FindOptions, diff --git a/packages/contracts/src/facades/command.ts b/packages/contracts/src/facades/command.ts index ae5c2ce614..3c31f8b26d 100644 --- a/packages/contracts/src/facades/command.ts +++ b/packages/contracts/src/facades/command.ts @@ -9,6 +9,7 @@ export { } from '../batch-contract.ts'; export type { DaemonBatchStep } from '../batch-step.ts'; export type { CliFlags, DaemonExcludedCliFlag } from '../cli-flags.ts'; +export type { CommandExecutionOptions, InternalRequestOptions } from '../request-envelope.ts'; export type { CommandFlags, MaestroRuntimeFlags } from '../command-flags.ts'; export type { DaemonWireRequest, DaemonWireRequestMeta } from '../daemon-wire-request.ts'; export type { DispatchedCommand } from '../dispatched-command.ts'; diff --git a/packages/contracts/src/request-envelope.ts b/packages/contracts/src/request-envelope.ts new file mode 100644 index 0000000000..e7fd3d8c76 --- /dev/null +++ b/packages/contracts/src/request-envelope.ts @@ -0,0 +1,95 @@ +// The internal request envelope every client call is projected into. The command façade carries it, +// not the client façade, so the package root's client types do not publish it. + +import type { AppsFilter } from './app-inventory.ts'; +import type { BackMode } from './back-mode.ts'; +import type { ClickButton } from './click-button.ts'; +import type { RecordingExportQuality } from './recording-export-quality.ts'; +import type { RecordingScope } from './recording-scope.ts'; +import type { ScreenshotRequestFlags } from './screenshot.ts'; +import type { SwipePattern } from './scroll-gesture.ts'; +import type { SessionSurface } from './session-surface.ts'; +import type { + DaemonInstallSource, + NetworkIncludeMode, + SessionRuntimeHints, +} from '@agent-device/kernel/contracts'; +import type { SnapshotCommandOptionFields } from '@agent-device/kernel/snapshot'; +import type { DaemonBatchStep } from './batch-step.ts'; +import type { ReplayRequestFields } from './replay-request-fields.ts'; +import type { AgentDeviceClientConfig, AgentDeviceSelectionOptions } from './client-connection.ts'; + +export type CommandExecutionOptions = Partial & + ReplayRequestFields & + SnapshotCommandOptionFields & { + positionals?: string[]; + kind?: string; + out?: string; + artifact?: string; + dsym?: string; + searchPath?: string; + count?: number; + fps?: number; + recordingScope?: RecordingScope; + quality?: RecordingExportQuality; + hideTouches?: boolean; + intervalMs?: number; + delayMs?: number; + durationMs?: number; + holdMs?: number; + jitterPx?: number; + pixels?: number; + /** Scroll: repeat passes until this selector is visible on screen. */ + until?: string; + doubleTap?: boolean; + verify?: boolean; + settle?: boolean; + settleQuietMs?: number; + clickButton?: ClickButton; + pauseMs?: number; + pattern?: SwipePattern; + headless?: boolean; + restart?: boolean; + replayBackend?: string; + findFirst?: boolean; + findLast?: boolean; + networkInclude?: NetworkIncludeMode; + batchOnError?: 'stop'; + batchMaxSteps?: number; + batchSteps?: DaemonBatchStep[]; + }; + +export type InternalRequestOptions = AgentDeviceClientConfig & + AgentDeviceSelectionOptions & + CommandExecutionOptions & { + runtime?: SessionRuntimeHints; + overlayRefs?: boolean; + surface?: SessionSurface; + activity?: string; + launchConsole?: string; + launchArgs?: string[]; + relaunch?: boolean; + shutdown?: boolean; + saveScript?: boolean | string; + /** #1258: overwrite an existing --save-script target instead of refusing. Alias: --overwrite. */ + force?: boolean; + testIme?: boolean; + noRecord?: boolean; + /** Fill-only script parameter name used to publish `${VAR}` instead of literal text. */ + recordAs?: string; + /** #1271 stage 2: force-record this action; mutually exclusive with `noRecord`. */ + record?: boolean; + backMode?: BackMode; + metroHost?: string; + metroPort?: number; + bundleUrl?: string; + launchUrl?: string; + appsFilter?: AppsFilter; + installSource?: DaemonInstallSource; + retainMaterializedPaths?: boolean; + materializedPathRetentionMs?: number; + materializationId?: string; + leaseTtlMs?: number; + provider?: string; + providerSessionId?: string; + }; diff --git a/src/agent-device-client.ts b/src/agent-device-client.ts index 89d0b19617..e616dabbc2 100644 --- a/src/agent-device-client.ts +++ b/src/agent-device-client.ts @@ -1,4 +1,4 @@ -import type { CommandFlags } from '@agent-device/contracts/command'; +import type { CommandFlags, InternalRequestOptions } from '@agent-device/contracts/command'; import { readSerializedSnapshotCaptureAnnotations, readSnapshotDiagnosticsSummary, @@ -18,7 +18,6 @@ import type { CaptureSnapshotResult, DragOptions, FlingOptions, - InternalRequestOptions, MaterializationReleaseOptions, PanOptions, PinchOptions, diff --git a/src/client/client-normalizers.ts b/src/client/client-normalizers.ts index 238eb36451..2659b7c57e 100644 --- a/src/client/client-normalizers.ts +++ b/src/client/client-normalizers.ts @@ -4,11 +4,11 @@ import type { AgentDeviceSessionDevice, AppDeployResult, AppInstallFromSourceResult, - InternalRequestOptions, MaterializationReleaseResult, StartupPerfSample, DeviceSelectionMetadata, } from '@agent-device/contracts/client'; +import type { InternalRequestOptions } from '@agent-device/contracts/command'; import type { TargetShutdownResult } from '@agent-device/contracts/device'; import { isAppleOs, diff --git a/src/client/lease-client.ts b/src/client/lease-client.ts index ebc9dee080..f914cc29f4 100644 --- a/src/client/lease-client.ts +++ b/src/client/lease-client.ts @@ -1,8 +1,5 @@ -import type { - HumanControlHold, - InternalRequestOptions, - Lease, -} from '@agent-device/contracts/client'; +import type { HumanControlHold, Lease } from '@agent-device/contracts/client'; +import type { InternalRequestOptions } from '@agent-device/contracts/command'; import { AppError } from '@agent-device/kernel/errors'; import { INTERNAL_COMMANDS } from '@agent-device/command-registry/catalog'; import { isRecord } from '@agent-device/kernel/record'; diff --git a/src/commands/__tests__/command-flags.test.ts b/src/commands/__tests__/command-flags.test.ts index 7110915220..412fba8c93 100644 --- a/src/commands/__tests__/command-flags.test.ts +++ b/src/commands/__tests__/command-flags.test.ts @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import { test } from 'vitest'; -import type { InternalRequestOptions } from '@agent-device/contracts/client'; +import type { InternalRequestOptions } from '@agent-device/contracts/command'; import { findCommandMetadata } from '../command-metadata.ts'; import { readMetadataCommandFlags } from '../command-flags.ts'; diff --git a/src/commands/cli-grammar/common.ts b/src/commands/cli-grammar/common.ts index 455d8848fb..b18260bf55 100644 --- a/src/commands/cli-grammar/common.ts +++ b/src/commands/cli-grammar/common.ts @@ -1,9 +1,5 @@ -import type { - ElementTarget, - InteractionTarget, - InternalRequestOptions, -} from '@agent-device/contracts/client'; -import type { CliFlags } from '@agent-device/contracts/command'; +import type { ElementTarget, InteractionTarget } from '@agent-device/contracts/client'; +import type { CliFlags, InternalRequestOptions } from '@agent-device/contracts/command'; import { AppError } from '@agent-device/kernel/errors'; import { checkElementTargetArgs, diff --git a/src/commands/cli-grammar/types.ts b/src/commands/cli-grammar/types.ts index 790e5505d3..fc2d3414ae 100644 --- a/src/commands/cli-grammar/types.ts +++ b/src/commands/cli-grammar/types.ts @@ -1,5 +1,8 @@ -import type { InternalRequestOptions } from '@agent-device/contracts/client'; -import type { CliFlags, CommandFlags } from '@agent-device/contracts/command'; +import type { + CliFlags, + CommandFlags, + InternalRequestOptions, +} from '@agent-device/contracts/command'; import type { ClickButton } from '@agent-device/contracts/click-button'; export type DaemonCommandRequest = { diff --git a/src/commands/command-flags.ts b/src/commands/command-flags.ts index 881a6345c1..4699c2c0bc 100644 --- a/src/commands/command-flags.ts +++ b/src/commands/command-flags.ts @@ -1,4 +1,4 @@ -import type { CommandFlags } from '@agent-device/contracts/command'; +import type { CommandFlags, InternalRequestOptions } from '@agent-device/contracts/command'; import { screenshotFlagsFromOptions } from '@agent-device/contracts/capture'; import { leaseScopeFromOptions, @@ -10,7 +10,6 @@ import { snapshotFlagsFromOptions, } from '@agent-device/kernel/snapshot'; import { getFlagDefinitions } from '@agent-device/command-registry/flag-registry'; -import type { InternalRequestOptions } from '@agent-device/contracts/client'; import type { CommandMetadata } from './command-contract.ts'; const CLI_FLAG_KEYS: ReadonlySet = new Set( diff --git a/test/wire-compat/closure-policy.ts b/test/wire-compat/closure-policy.ts index c011b2de5d..98d9d72172 100644 --- a/test/wire-compat/closure-policy.ts +++ b/test/wire-compat/closure-policy.ts @@ -73,7 +73,7 @@ export const WIRE_CLOSURE_WAIVERS: Readonly> = { // If a flag ever becomes a typed field on DaemonRequest, that changes // DaemonRequest's own digest, and the right fix is to list the type here // rather than widen this waiver. - 'packages/contracts/src/client-request.ts#InternalRequestOptions': + 'packages/contracts/src/request-envelope.ts#InternalRequestOptions': 'CLI-side option projection; reaches the peer inside DaemonRequest.input/flags (Record, both listed), and ADR 0006 calls new flags additive.', 'packages/contracts/src/command-flags.ts#CommandFlags': 'CLI-side flag vocabulary; reaches the peer inside DaemonRequest.flags (Record, listed), and ADR 0006 calls new flags additive.',