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
80 changes: 79 additions & 1 deletion src/commands/management/prepare.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,97 @@
import { PUBLIC_COMMANDS } from '@agent-device/command-registry/catalog';
import type { CommandSchemaOverride } from '@agent-device/command-registry/command-schema';
import { enumField, integerField, requiredField } from '../command-input.ts';
import type { CommandResultMap } from '@agent-device/command-registry/command-result';
import { DEVICE_KINDS, PUBLIC_PLATFORMS } from '@agent-device/kernel/device';
import {
constSchema,
enumField,
enumSchema,
integerField,
numberSchema,
objectSchema,
requiredField,
stringSchema,
} from '../command-input.ts';
import {
commonInputFromFlags,
direct,
requiredDaemonString,
requiredString,
} from '../cli-grammar/common.ts';
import type { CliReader, DaemonWriter } from '../cli-grammar/types.ts';
import type { JsonSchema } from '../command-contract.ts';
import { defineCommandFacet } from '../family/types.ts';
import { defineFieldCommandMetadata } from '../field-command-contract.ts';
import { managementCliOutputFormatters } from './output.ts';

const PREPARE_ACTION_VALUES = ['ios-runner'] as const;

/**
* This family's advertised MCP `outputSchema`, keyed by daemon command name and projected into
* the command map by `src/mcp/command-output-schemas.ts`. Non-strict like every other entry: no
* `additionalProperties: false`, so additive response fields keep validating. prepare is not
* MCP-exposed, but the schema stays map-complete with `CommandResultMap`.
*/
export const PREPARE_COMMAND_OUTPUT_SCHEMAS = {
// packages/contracts/src/prepare.ts
prepare: objectSchema(
{
action: constSchema('ios-runner'),
// PublicPlatform leaf, mirroring PrepareCommandResult (packages/contracts/src/prepare.ts).
platform: enumSchema(PUBLIC_PLATFORMS),
deviceId: stringSchema(),
deviceName: stringSchema(),
kind: enumSchema(DEVICE_KINDS),
durationMs: numberSchema(),
runner: objectSchema({}, []),
cache: enumSchema(['exact', 'miss', 'external']),
artifact: enumSchema(['valid', 'rebuilt']),
buildMs: numberSchema(),
connectMs: numberSchema(),
healthCheckMs: numberSchema(),
xctestrunPath: stringSchema(),
recoveryReason: stringSchema(),
failureReason: stringSchema(),
timing: objectSchema(
{
totalMs: numberSchema(),
additiveParts: objectSchema(
{
buildMs: numberSchema(),
connectAfterBuildMs: numberSchema(),
healthCheckMs: numberSchema(),
},
['connectAfterBuildMs', 'healthCheckMs'],
),
containment: objectSchema(
{
connectMs: { type: 'array', items: constSchema('buildMs') },
healthCheckMs: { type: 'array', items: stringSchema() },
},
['healthCheckMs'],
),
note: stringSchema(),
},
['totalMs', 'additiveParts', 'containment', 'note'],
),
message: stringSchema(),
},
[
'action',
'platform',
'deviceId',
'deviceName',
'kind',
'durationMs',
'runner',
'connectMs',
'healthCheckMs',
'timing',
'message',
],
),
} satisfies Pick<Record<keyof CommandResultMap, JsonSchema>, 'prepare'>;

const prepareCommandMetadata = defineFieldCommandMetadata(
'prepare',
'Prepare platform helper infrastructure. ios-runner builds/reuses, starts, and health-checks the XCTest runner so later Apple snapshots and interactions do not pay first-use startup cost. In JSON output, top-level buildMs/connectMs/healthCheckMs are diagnostic fields and may overlap; use timing.additiveParts for additive wall-clock phase totals. In CI, run it after boot/install and before replay/test; if replay/test starts a separate daemon, stop the prepare daemon before replay/test so it does not keep the prepared runner lease. It is not a recovery step for "runner already owned by another agent-device daemon"; stop the owning daemon on the Mac with simulator access instead. Runner build/start output is written to the session runner.log; daemon.log is for daemon lifecycle/startup issues.',
Expand Down
31 changes: 31 additions & 0 deletions src/mcp/__tests__/command-tools-management-prepare-schemas.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import assert from 'node:assert/strict';
import { test } from 'vitest';
import { ownerFilesForCommand } from '@agent-device/command-registry/owner-files';
import { PREPARE_COMMAND_OUTPUT_SCHEMAS } from '../../commands/management/prepare.ts';
import { COMMAND_OUTPUT_SCHEMAS } from '../command-output-schemas.ts';

const PREPARE_COMMANDS = Object.keys(PREPARE_COMMAND_OUTPUT_SCHEMAS) as Array<
keyof typeof PREPARE_COMMAND_OUTPUT_SCHEMAS
>;

// prepare does not carry the post-action observation trait (#1652): it is a device-runtime
// command, not an interaction command, so the composed map never copies the entry and
// reference equality must hold.
test('MCP prepare family output schemas are the family module entries, not copies', () => {
for (const command of PREPARE_COMMANDS) {
assert.equal(
COMMAND_OUTPUT_SCHEMAS[command],
PREPARE_COMMAND_OUTPUT_SCHEMAS[command],
`${command} is not reference-equal to the prepare module's own schema object`,
);
}
});

test('the projected family map declares exactly the commands its module owns', () => {
for (const command of PREPARE_COMMANDS) {
assert.ok(
ownerFilesForCommand(command).includes('src/commands/management/prepare.ts'),
`${command} projects its output schema from this module but does not name it as its owner`,
);
}
});
2 changes: 2 additions & 0 deletions src/mcp/__tests__/command-tools-replay-schemas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { ReplayCommandResult, ReplaySuiteResult } from '@agent-device/contr
import { ownerFilesForCommand } from '@agent-device/command-registry/owner-files';
import { commandSupportsSettleObservation } from '@agent-device/command-registry/registry';
import { WAIT_COMMAND_OUTPUT_SCHEMAS } from '../../commands/capture/wait.ts';
import { PREPARE_COMMAND_OUTPUT_SCHEMAS } from '../../commands/management/prepare.ts';
import { DEVICE_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS } from '../../commands/management/device.ts';
import { PUSH_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS } from '../../commands/management/push.ts';
import { VIEWPORT_COMMAND_OUTPUT_SCHEMAS } from '../../commands/management/viewport.ts';
Expand Down Expand Up @@ -79,6 +80,7 @@ const PROJECTED_FAMILIES = [
{ name: 'system', schemas: SYSTEM_COMMAND_OUTPUT_SCHEMAS },
{ name: 'interaction', schemas: INTERACTION_COMMAND_OUTPUT_SCHEMAS },
{ name: 'capture-wait', schemas: WAIT_COMMAND_OUTPUT_SCHEMAS },
{ name: 'management-prepare', schemas: PREPARE_COMMAND_OUTPUT_SCHEMAS },
{ name: 'management-device', schemas: DEVICE_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS },
{ name: 'management-push', schemas: PUSH_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS },
{ name: 'management-viewport', schemas: VIEWPORT_COMMAND_OUTPUT_SCHEMAS },
Expand Down
63 changes: 3 additions & 60 deletions src/mcp/command-output-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
stringSchema,
} from '../commands/command-input.ts';
import { WAIT_COMMAND_OUTPUT_SCHEMAS } from '../commands/capture/wait.ts';
import { PREPARE_COMMAND_OUTPUT_SCHEMAS } from '../commands/management/prepare.ts';
import { DEVICE_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS } from '../commands/management/device.ts';
import { PUSH_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS } from '../commands/management/push.ts';
import { VIEWPORT_COMMAND_OUTPUT_SCHEMAS } from '../commands/management/viewport.ts';
Expand All @@ -22,7 +23,7 @@ import {
import { RECORDING_COMMAND_OUTPUT_SCHEMAS } from '../commands/recording/output-schemas.ts';
import { REPLAY_COMMAND_OUTPUT_SCHEMAS } from '../commands/replay/index.ts';
import { SYSTEM_COMMAND_OUTPUT_SCHEMAS } from '../commands/system/index.ts';
import { DEVICE_KINDS, DEVICE_TARGETS, PUBLIC_PLATFORMS } from '@agent-device/kernel/device';
import { DEVICE_TARGETS } from '@agent-device/kernel/device';

/**
* Registry of per-command MCP `outputSchema`s, keyed by the daemon command
Expand Down Expand Up @@ -183,65 +184,6 @@ const BASE_COMMAND_OUTPUT_SCHEMAS = {
['direction'],
),

// packages/contracts/src/prepare.ts — prepare is not MCP-exposed, but the schema stays
// map-complete with CommandResultMap.
prepare: objectSchema(
{
action: constSchema('ios-runner'),
// PublicPlatform leaf, mirroring PrepareCommandResult (packages/contracts/src/prepare.ts).
platform: enumSchema(PUBLIC_PLATFORMS),
deviceId: stringSchema(),
deviceName: stringSchema(),
kind: enumSchema(DEVICE_KINDS),
durationMs: numberSchema(),
runner: objectSchema({}, []),
cache: enumSchema(['exact', 'miss', 'external']),
artifact: enumSchema(['valid', 'rebuilt']),
buildMs: numberSchema(),
connectMs: numberSchema(),
healthCheckMs: numberSchema(),
xctestrunPath: stringSchema(),
recoveryReason: stringSchema(),
failureReason: stringSchema(),
timing: objectSchema(
{
totalMs: numberSchema(),
additiveParts: objectSchema(
{
buildMs: numberSchema(),
connectAfterBuildMs: numberSchema(),
healthCheckMs: numberSchema(),
},
['connectAfterBuildMs', 'healthCheckMs'],
),
containment: objectSchema(
{
connectMs: { type: 'array', items: constSchema('buildMs') },
healthCheckMs: { type: 'array', items: stringSchema() },
},
['healthCheckMs'],
),
note: stringSchema(),
},
['totalMs', 'additiveParts', 'containment', 'note'],
),
message: stringSchema(),
},
[
'action',
'platform',
'deviceId',
'deviceName',
'kind',
'durationMs',
'runner',
'connectMs',
'healthCheckMs',
'timing',
'message',
],
),

// packages/contracts/src/doctor.ts
doctor: objectSchema(
{
Expand Down Expand Up @@ -304,6 +246,7 @@ const BASE_COMMAND_OUTPUT_SCHEMAS = {
// a projected command then fails as TS2783 instead of quietly overriding the family's,
// and this map's `satisfies` still refuses a missing `CommandResultMap` key.
...WAIT_COMMAND_OUTPUT_SCHEMAS,
...PREPARE_COMMAND_OUTPUT_SCHEMAS,
...DEVICE_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS,
...PUSH_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS,
...VIEWPORT_COMMAND_OUTPUT_SCHEMAS,
Expand Down
Loading