Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

- Breaking: removed the deprecated `--session-locked` and `--session-lock-conflicts` flags. Use `--session-lock reject|strip` instead; passing either old flag now fails with `Unknown flag: ... Use --session-lock reject|strip instead.`
- Breaking: removed the `replay export --format` flag. `replay export` always writes Maestro YAML.
- Breaking: removed the unused `LeaseAllocatePayload`, `LeaseHeartbeatPayload`, and `LeaseReleasePayload` type exports from `agent-device/contracts`. Lease request metadata is fully described by `DaemonRequestMeta`.
- Maestro compat: `assertVisible` and `assertNotVisible` now accept `childOf` for ancestor scoping, matching `tapOn` (#1294).
- Breaking: removed deprecated gesture duration and rotate velocity inputs (#1218).
- `swipe x1 y1 x2 y2` no longer accepts a trailing `durationMs` positional; use `gesture pan x1 y1 (x2-x1) (y2-y1) durationMs` for deliberate timed drags.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ struct Command: Codable {
let allowNonHittableCoordinateFallback: Bool?
let delayMs: Int?
let textEntryMode: String?
let clearFirst: Bool?
let action: String?
let x: Double?
let y: Double?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ extension RunnerTests {
case "replace":
return .replacement
default:
return command.clearFirst == true ? .replacement : .none
return .none
}
}

Expand Down
9 changes: 1 addition & 8 deletions scripts/check-bundle-owner-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { COMMAND_OWNER_FILES } from '../src/core/command-descriptor/owner-files.ts';
import { getDaemonRouteOwnerFiles } from '../src/daemon/route-owner-files.ts';
import { walkFiles } from './lib/walk-files.ts';

const repoRoot = path.resolve(import.meta.dirname, '..');
const distRoot = path.join(repoRoot, 'dist', 'src');
Expand Down Expand Up @@ -31,11 +32,3 @@ if (leaks.length > 0) {
process.stdout.write(
`Verified the ownerFiles key and ${ownerPaths.size} owner-file paths are absent from ${bundleFiles.length} production bundles.\n`,
);

function walkFiles(root: string): string[] {
if (!fs.existsSync(root)) return [];
return fs.readdirSync(root, { withFileTypes: true }).flatMap((entry) => {
const entryPath = path.join(root, entry.name);
return entry.isDirectory() ? walkFiles(entryPath) : [entryPath];
});
}
22 changes: 6 additions & 16 deletions scripts/integration-progress-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from 'node:path';
import { PUBLIC_COMMANDS } from '../src/command-catalog.ts';
import { listCommandMetadata } from '../src/commands/command-metadata.ts';
import { getFlagDefinitions } from '../src/commands/cli-grammar/flag-registry.ts';
import { walkFiles } from './lib/walk-files.ts';

const EMPTY_COVERAGE_METRIC = { pct: 0 };
const EMPTY_STATEMENT_COVERAGE = { covered: 0, pct: 0, total: 0 };
Expand All @@ -11,14 +12,14 @@ export function buildIntegrationProgressModel({ root = process.cwd() } = {}) {
const coverageSummary = path.join(root, 'coverage/coverage-summary.json');
const handlerTestDir = path.join(root, 'src/daemon/handlers/__tests__');
const providerScenarioDir = path.join(root, 'test/integration/provider-scenarios');
const commandContractFiles = listFiles(path.join(root, 'src/commands'), (file) =>
const commandContractFiles = walkFiles(path.join(root, 'src/commands'), (file) =>
isCommandContractSource(file),
);
const clientCommandMethods = readClientCommandMethods(commandContractFiles);

const handlerTests = listFiles(handlerTestDir, (file) => file.endsWith('.test.ts'));
const providerScenarioTests = listFiles(providerScenarioDir, (file) => file.endsWith('.test.ts'));
const providerScenarioSources = listFiles(providerScenarioDir, (file) => file.endsWith('.ts'));
const handlerTests = walkFiles(handlerTestDir, (file) => file.endsWith('.test.ts'));
const providerScenarioTests = walkFiles(providerScenarioDir, (file) => file.endsWith('.test.ts'));
const providerScenarioSources = walkFiles(providerScenarioDir, (file) => file.endsWith('.ts'));
const providerScenarioSupportSources = providerScenarioSources.filter(
(file) => !file.endsWith('.test.ts'),
);
Expand Down Expand Up @@ -238,7 +239,7 @@ function summarizeProviderScenarioFlagExclusions() {
{
name: 'remote connection and session-lock policy',
owner: 'connection/runtime/request policy tests',
keys: ['force', 'noLogin', 'sessionLock', 'sessionLocked', 'sessionLockConflicts'],
keys: ['force', 'noLogin', 'sessionLock'],
},
{
name: 'cloud artifact provider lookup',
Expand Down Expand Up @@ -309,7 +310,6 @@ function summarizeProviderScenarioFlagExclusions() {
'reporter',
'reportJunit',
'replayMaestro',
'replayExportFormat',
'recordVideo',
'shardAll',
'shardSplit',
Expand Down Expand Up @@ -345,16 +345,6 @@ function readPublicCliFlagKeys() {
);
}

function listFiles(dir, predicate) {
if (!fs.existsSync(dir)) return [];
const entries = fs.readdirSync(dir, { withFileTypes: true });
return entries.flatMap((entry) => {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) return listFiles(fullPath, predicate);
return predicate(fullPath) ? [fullPath] : [];
});
}

function isCommandContractSource(file) {
return (
file.endsWith('.ts') &&
Expand Down
11 changes: 11 additions & 0 deletions scripts/lib/walk-files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import fs from 'node:fs';
import path from 'node:path';

export function walkFiles(root: string, predicate?: (file: string) => boolean): string[] {
if (!fs.existsSync(root)) return [];
return fs.readdirSync(root, { withFileTypes: true }).flatMap((entry) => {
const entryPath = path.join(root, entry.name);
if (entry.isDirectory()) return walkFiles(entryPath, predicate);
return !predicate || predicate(entryPath) ? [entryPath] : [];
});
}
1 change: 0 additions & 1 deletion src/__tests__/cli-client-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,6 @@ click text="Continue"
json: false,
help: false,
version: false,
replayExportFormat: 'maestro',
out: outPath,
},
client,
Expand Down
126 changes: 0 additions & 126 deletions src/__tests__/default-cloud-artifact-provider.test.ts

This file was deleted.

4 changes: 1 addition & 3 deletions src/__tests__/runtime-public.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
type AgentDevice,
type CommandSessionStore,
} from '../runtime.ts';
import { BACKEND_CAPABILITY_NAMES, type AgentDeviceBackend } from '../backend.ts';
import type { AgentDeviceBackend } from '../backend.ts';
import { commands, type ScreenshotCommandOptions } from '../commands/index.ts';
import {
createLocalArtifactAdapter,
Expand Down Expand Up @@ -235,7 +235,6 @@ test('internal backend, commands, and io modules are usable', () => {
const options = {
out: { kind: 'path', path: '/tmp/screen.png' },
} satisfies ScreenshotCommandOptions;
assert.equal(BACKEND_CAPABILITY_NAMES.includes('android.shell'), true);
assert.equal(options.out.kind, 'path');
assert.equal(typeof commands.capture.screenshot, 'function');
assert.equal(typeof commands.capture.diffScreenshot, 'function');
Expand All @@ -259,7 +258,6 @@ test('internal backend, commands, and io modules are usable', () => {
assert.equal(typeof commands.system.back, 'function');
assert.equal(typeof commands.system.home, 'function');
assert.equal(typeof commands.system.orientation, 'function');
assert.equal(typeof commands.system.rotate, 'function');
assert.equal(typeof commands.system.keyboard, 'function');
assert.equal(typeof commands.system.clipboard, 'function');
assert.equal(typeof commands.system.settings, 'function');
Expand Down
11 changes: 0 additions & 11 deletions src/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,6 @@ import type { ScreenshotResultData } from './utils/screenshot-result.ts';
// leaf string, not the internal collapsed `apple`.
export type AgentDeviceBackendPlatform = PublicPlatform;

export const BACKEND_CAPABILITY_NAMES = [
'android.shell',
'ios.runnerCommand',
'macos.desktopScreenshot',
] as const;

export type BackendCapabilityName = (typeof BACKEND_CAPABILITY_NAMES)[number];

export type BackendCapabilitySet = readonly BackendCapabilityName[];

export type BackendCommandContext = {
session?: string;
requestId?: string;
Expand Down Expand Up @@ -419,7 +409,6 @@ export type BackendEscapeHatches = {

export type AgentDeviceBackend = {
platform: AgentDeviceBackendPlatform;
capabilities?: BackendCapabilitySet;
escapeHatches?: BackendEscapeHatches;
captureSnapshot?(
context: BackendCommandContext,
Expand Down
12 changes: 10 additions & 2 deletions src/cli-schema/command-schema-guards.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
listCliCommandNames,
SPECIAL_CLI_COMMANDS,
} from '../command-catalog.ts';
import { listCapabilityCheckedCommandNames } from '../core/command-descriptor/registry.ts';
import { commandDescriptors } from '../core/command-descriptor/registry.ts';
import { getCliCommandSchema } from './command-schema.ts';

test('every public capability command has a parser schema entry', () => {
Expand Down Expand Up @@ -57,7 +57,15 @@ test('cli.ts command dispatch checks are recognized by parser-level unknown-comm
});

test('schema capability mappings match capability source-of-truth', () => {
assert.deepEqual(listCapabilityCheckedCommandNames(), listCapabilityCommands());
const cliCommands = new Set<string>(listCliCommandNames());
const capabilityCheckedCommands = commandDescriptors
.filter(
(descriptor) =>
'capability' in descriptor && descriptor.capability && cliCommands.has(descriptor.name),
)
.map((descriptor) => descriptor.name)
.sort();
assert.deepEqual(capabilityCheckedCommands, listCapabilityCommands());
});

function collectCliDispatchCommandLiterals(): Set<string> {
Expand Down
7 changes: 1 addition & 6 deletions src/cli-schema/option-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,7 @@ const CONFIG_EXCLUDED_FLAG_KEYS = new Set<FlagKey>([
'githubActionsArtifact',
]);

const ENV_EXCLUDED_FLAG_KEYS = new Set<FlagKey>([
'appsFilter',
'iosSimulatorDeviceSet',
'sessionLocked',
'sessionLockConflicts',
]);
const ENV_EXCLUDED_FLAG_KEYS = new Set<FlagKey>(['appsFilter', 'iosSimulatorDeviceSet']);

const optionSpecs = buildOptionSpecs();
const optionSpecByKey = new Map(optionSpecs.map((spec) => [spec.key, spec]));
Expand Down
13 changes: 3 additions & 10 deletions src/cli/commands/replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,8 @@ function handleReplayRunCommand({ positionals, flags }: ReplayCommandParams): fa
if (positionals.length > 1) {
throw new AppError('INVALID_ARGS', 'replay accepts exactly one input path: replay <path>');
}
if (flags.replayExportFormat !== undefined || flags.out !== undefined) {
throw new AppError(
'INVALID_ARGS',
'replay --format/--out are only supported with replay export.',
);
if (flags.out !== undefined) {
throw new AppError('INVALID_ARGS', 'replay --out is only supported with replay export.');
}
return false;
}
Expand Down Expand Up @@ -54,7 +51,7 @@ async function handleReplayExportCommand({
writeCommandOutput(
flags,
{
format: flags.replayExportFormat ?? 'maestro',
format: 'maestro',
sourcePath,
...(outputPath ? { path: outputPath } : { yaml: result.yaml }),
warnings: result.warnings,
Expand Down Expand Up @@ -83,8 +80,4 @@ function validateReplayExportOptions(
if (flags.replayEnv?.length) {
throw new AppError('INVALID_ARGS', 'replay export does not evaluate --env substitutions.');
}
const format = flags.replayExportFormat ?? 'maestro';
if (format !== 'maestro') {
throw new AppError('INVALID_ARGS', `Unsupported replay export format: ${format}`);
}
}
9 changes: 9 additions & 0 deletions src/cli/connection/client-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import crypto from 'node:crypto';

export function buildConnectClientId(...parts: Array<string | undefined>): string {
return crypto
.createHash('sha256')
.update(parts.map((part) => part ?? '').join('\0'))
.digest('hex')
.slice(0, 16);
}
Loading
Loading