Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
937796a
refactor: migrate back/home/orientation/tv-remote/keyboard to the req…
thymikee Aug 22, 2026
6a4d937
refactor: extract shared interactor-resolution prelude in keyboard-ru…
thymikee Aug 22, 2026
8e10d4d
fix: refuse watchOS admission for back/home/orientation/keyboard; pin…
thymikee Aug 22, 2026
a5cf01a
fix: stop orientation-runtime.test.ts's router-join test from hitting…
thymikee Aug 22, 2026
1f56e43
refactor: extract navigation/keyboard concepts into sibling modules; …
thymikee Aug 22, 2026
d10229a
refactor: adopt granular contracts subpaths for back/home/orientation…
thymikee Aug 22, 2026
ab748f0
refactor: shared navigation/keyboard binder table; dedupe keyboard ad…
thymikee Aug 22, 2026
f4a59fa
refactor: split generic-mutating command traits from the legacy dispa…
thymikee Aug 22, 2026
33d9924
refactor: table-ify packages/contracts/src/keyboard-runtime.ts's thre…
thymikee Aug 22, 2026
5a68d65
refactor: parameterize runSessionOrSelectorDispatch with an execute s…
thymikee Aug 22, 2026
2af0bd0
refactor: extract limrun facts-runtime.ts; discriminate KeyboardDismi…
thymikee Aug 22, 2026
8f945f9
fix: expire ref frame before the mutating call, not after; extract se…
thymikee Aug 22, 2026
fc6e081
style: reformat legacySessionDispatchExecute call in session-selector…
thymikee Aug 22, 2026
5cd8ebe
refactor: derive catalog operation list from one canonical tuple; mov…
thymikee Aug 23, 2026
472d0db
fix: restore back/home/orientation/tv-remote/keyboard-runtime exports…
thymikee Aug 24, 2026
120b5f2
test: pin the exact point the live iOS email field value goes missing
thymikee Aug 24, 2026
faec48b
refactor: make keyboard status/enter owner-discriminated too; trim re…
thymikee Aug 24, 2026
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
24 changes: 24 additions & 0 deletions packages/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@
"types": "./src/back-mode.ts",
"default": "./src/back-mode.ts"
},
"./back-runtime": {
"types": "./src/back-runtime.ts",
"default": "./src/back-runtime.ts"
},
"./capture": {
"types": "./src/facades/capture.ts",
"default": "./src/facades/capture.ts"
Expand Down Expand Up @@ -144,6 +148,10 @@
"types": "./src/gesture-plan-types.ts",
"default": "./src/gesture-plan-types.ts"
},
"./home-runtime": {
"types": "./src/home-runtime.ts",
"default": "./src/home-runtime.ts"
},
"./interaction": {
"types": "./src/facades/interaction.ts",
"default": "./src/facades/interaction.ts"
Expand All @@ -156,10 +164,18 @@
"types": "./src/interaction-guarantees.ts",
"default": "./src/interaction-guarantees.ts"
},
"./interactor-operation-catalog": {
"types": "./src/interactor-operation-catalog.ts",
"default": "./src/interactor-operation-catalog.ts"
},
"./interactor-types": {
"types": "./src/interactor-types.ts",
"default": "./src/interactor-types.ts"
},
"./keyboard-runtime": {
"types": "./src/keyboard-runtime.ts",
"default": "./src/keyboard-runtime.ts"
},
"./logs-runtime-plan": {
"types": "./src/logs-runtime-plan.ts",
"default": "./src/logs-runtime-plan.ts"
Expand All @@ -180,6 +196,10 @@
"types": "./src/facades/observability.ts",
"default": "./src/facades/observability.ts"
},
"./orientation-runtime": {
"types": "./src/orientation-runtime.ts",
"default": "./src/orientation-runtime.ts"
},
"./platform": {
"types": "./src/facades/platform.ts",
"default": "./src/facades/platform.ts"
Expand Down Expand Up @@ -272,6 +292,10 @@
"types": "./src/tv-remote.ts",
"default": "./src/tv-remote.ts"
},
"./tv-remote-runtime": {
"types": "./src/tv-remote-runtime.ts",
"default": "./src/tv-remote-runtime.ts"
},
"./type-text-runtime": {
"types": "./src/type-text-runtime.ts",
"default": "./src/type-text-runtime.ts"
Expand Down
87 changes: 87 additions & 0 deletions packages/contracts/src/back-runtime.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { expect, test, vi } from 'vitest';
import {
bindLocalBackInteractor,
bindProviderBackInteractor,
backRuntimeOperationFacts,
} from './back-runtime.ts';
import type { Interactor } from './interactor-types.ts';

const device = {
platform: 'android',
id: 'emulator-5554',
name: 'Pixel',
kind: 'emulator',
booted: true,
} as const;

test('builds the exact back operation fact catalog', () => {
const back = { available: true } as const;
expect(backRuntimeOperationFacts({ back })).toEqual({ back });
});

test('a local binding drives the interactor with the requested mode', async () => {
const back = vi.fn(async () => undefined);
const resolveInteractor = vi.fn(async () => ({ back }) as unknown as Interactor);
const signal = new AbortController().signal;

const operations = bindLocalBackInteractor({ device, signal, resolveInteractor });
await operations.back({
mode: 'system',
options: { appBundleId: 'com.example.app' },
execution: { logPath: '/tmp/daemon.log', requestId: 'back-1' },
});

expect(resolveInteractor).toHaveBeenCalledWith(device, {
logPath: '/tmp/daemon.log',
requestId: 'back-1',
appBundleId: 'com.example.app',
signal,
});
expect(back).toHaveBeenCalledWith('system');
});

test('a provider binding drives its own resolved interactor', async () => {
const back = vi.fn(async () => undefined);
const resolveInteractor = vi.fn(() => ({ back }) as unknown as Interactor);
const signal = new AbortController().signal;

const operations = bindProviderBackInteractor({ device, signal, resolveInteractor });
await operations.back({ execution: { requestId: 'back-2' } });

expect(resolveInteractor).toHaveBeenCalledWith({
requestId: 'back-2',
appBundleId: undefined,
signal,
});
expect(back).toHaveBeenCalledWith(undefined);
});

test('a provider binding fails closed when its exact owner exposes no interactor', async () => {
const operations = bindProviderBackInteractor({
device,
signal: new AbortController().signal,
resolveInteractor: () => undefined,
});

await expect(operations.back({})).rejects.toMatchObject({
code: 'UNSUPPORTED_OPERATION',
details: { reason: 'provider-runtime-interactor-missing', deviceId: device.id },
});
});

test('an already-cancelled request never resolves an interactor', async () => {
const controller = new AbortController();
controller.abort();
const back = vi.fn(async () => undefined);
const resolveInteractor = vi.fn(async () => ({ back }) as unknown as Interactor);

const operations = bindLocalBackInteractor({
device,
signal: controller.signal,
resolveInteractor,
});

await expect(operations.back({})).rejects.toThrow();
expect(resolveInteractor).not.toHaveBeenCalled();
expect(back).not.toHaveBeenCalled();
});
86 changes: 86 additions & 0 deletions packages/contracts/src/back-runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type { DeviceInfo } from '@agent-device/kernel/device';
import {
localInteractorSource,
providerInteractorSource,
type LocalInteractorOperationResolver,
type ProviderInteractorOperationResolver,
} from './interactor-operation-binding.ts';
import type { BackMode, Interactor, RunnerContext } from './interactor-types.ts';
import type { RuntimeOperationFact } from './platform-runtime.ts';
import type { SnapshotRuntimeExecution } from './snapshot-runtime.ts';

/**
* Neutral intent for one back navigation. `mode` is already validated by the caller (the
* `--mode` flag); the operation names no command, request, session, or CLI flag.
*/
export type BackInput = Readonly<{
mode?: BackMode;
options?: Readonly<{ appBundleId?: string }>;
/** Same runner metadata a capture needs; reuses that type rather than restating it. */
execution?: SnapshotRuntimeExecution;
}>;

/**
* Back returns nothing. The legacy leaf discarded whatever the interactor answered and reported
* only the mode it requested, so a result type here would be a surface the command never had.
*/
export type BackRuntimeOperations = Readonly<{
back(input: BackInput): Promise<void>;
}>;

export type BackRuntimeOperationFacts = Readonly<{
back: RuntimeOperationFact;
}>;

export function backRuntimeOperationFacts(
input: Readonly<{ back: RuntimeOperationFact }>,
): BackRuntimeOperationFacts {
return Object.freeze({ back: input.back });
}

/**
* Captures one selected owner's interactor authority for the lifetime of a request binding. The
* owner is already chosen by the time a binder is called, so each entry point supplies its own
* resolution and this holds only what both share: the runner context and the navigation itself.
*/
function bindBack(
signal: AbortSignal,
resolveInteractor: (runner: RunnerContext) => Promise<Interactor>,
): BackRuntimeOperations {
return Object.freeze({
back: async (input: BackInput) => {
signal.throwIfAborted();
const interactor = await resolveInteractor({
...input.execution,
appBundleId: input.options?.appBundleId,
signal,
});
await interactor.back(input.mode);
},
});
}

export type LocalBackInteractorResolver = LocalInteractorOperationResolver;

export function bindLocalBackInteractor(
params: Readonly<{
device: DeviceInfo;
signal: AbortSignal;
resolveInteractor: LocalBackInteractorResolver;
}>,
): BackRuntimeOperations {
return bindBack(params.signal, localInteractorSource(params));
}

export type ProviderBackInteractorResolver = ProviderInteractorOperationResolver;

/** Provider bindings fail closed when their exact owner no longer exposes its interactor. */
export function bindProviderBackInteractor(
params: Readonly<{
device: DeviceInfo;
signal: AbortSignal;
resolveInteractor: ProviderBackInteractorResolver;
}>,
): BackRuntimeOperations {
return bindBack(params.signal, providerInteractorSource({ ...params, operation: 'back' }));
}
3 changes: 3 additions & 0 deletions packages/contracts/src/facades/interaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ export type {
FillUnconfirmedVerification,
FillVerificationTarget,
Interactor,
KeyboardDismissResult,
KeyboardEnterResult,
KeyboardStatusResult,
RunnerCallOptions,
RunnerContext,
ScreenshotOptions,
Expand Down
79 changes: 79 additions & 0 deletions packages/contracts/src/facades/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,14 @@ export {
focusRuntimeUse,
typeTextRuntimeUse,
viewportRuntimeUse,
backRuntimeUse,
homeRuntimeUse,
orientationRuntimeUse,
tvRemoteRuntimeUse,
keyboardRuntimePlanUses,
keyboardStatusUse,
keyboardDismissUse,
keyboardEnterUse,
} from '../platform-runtime-operations.ts';
export type {
ScreenshotRuntimePlan,
Expand Down Expand Up @@ -315,6 +323,77 @@ export type {
TypeTextRuntimeOperationFacts,
TypeTextRuntimeOperations,
} from '../type-text-runtime.ts';
export {
bindLocalBackInteractor,
bindProviderBackInteractor,
backRuntimeOperationFacts,
} from '../back-runtime.ts';
export type {
BackInput,
BackRuntimeOperationFacts,
BackRuntimeOperations,
LocalBackInteractorResolver,
ProviderBackInteractorResolver,
} from '../back-runtime.ts';
export {
bindLocalHomeInteractor,
bindProviderHomeInteractor,
homeRuntimeOperationFacts,
} from '../home-runtime.ts';
export type {
HomeInput,
HomeRuntimeOperationFacts,
HomeRuntimeOperations,
LocalHomeInteractorResolver,
ProviderHomeInteractorResolver,
} from '../home-runtime.ts';
export {
bindLocalOrientationInteractor,
bindProviderOrientationInteractor,
orientationRuntimeOperationFacts,
} from '../orientation-runtime.ts';
export type {
LocalOrientationInteractorResolver,
OrientationRuntimeOperationFacts,
OrientationRuntimeOperations,
ProviderOrientationInteractorResolver,
SetOrientationInput,
SetOrientationResult,
} from '../orientation-runtime.ts';
export {
bindLocalTvRemoteInteractor,
bindProviderTvRemoteInteractor,
tvRemoteRuntimeOperationFacts,
} from '../tv-remote-runtime.ts';
export type {
LocalTvRemoteInteractorResolver,
ProviderTvRemoteInteractorResolver,
TvRemoteInput,
TvRemoteRuntimeOperationFacts,
TvRemoteRuntimeOperations,
} from '../tv-remote-runtime.ts';
export {
bindLocalKeyboardStatusInteractor,
bindProviderKeyboardStatusInteractor,
bindLocalKeyboardDismissInteractor,
bindProviderKeyboardDismissInteractor,
bindLocalKeyboardEnterInteractor,
bindProviderKeyboardEnterInteractor,
keyboardRuntimeOperationFacts,
} from '../keyboard-runtime.ts';
export type {
KeyboardActionInput,
KeyboardDismissResult,
KeyboardDismissRuntimeOperations,
KeyboardEnterResult,
KeyboardEnterRuntimeOperations,
KeyboardRuntimeOperationFacts,
KeyboardRuntimeOperations,
KeyboardStatusResult,
KeyboardStatusRuntimeOperations,
LocalKeyboardInteractorResolver,
ProviderKeyboardInteractorResolver,
} from '../keyboard-runtime.ts';
export { viewportRuntimeOperationFacts } from '../viewport-runtime.ts';
export type {
SetViewportInput,
Expand Down
Loading
Loading