diff --git a/packages/daemon/src/__tests__/support/touch-command-surface-fake.ts b/packages/daemon/src/__tests__/support/touch-command-surface-fake.ts index 1791d8760a..97fa0034e7 100644 --- a/packages/daemon/src/__tests__/support/touch-command-surface-fake.ts +++ b/packages/daemon/src/__tests__/support/touch-command-surface-fake.ts @@ -23,10 +23,20 @@ import { singlePointerPlanEndpoints, } from '@agent-device/contracts/gesture-plan'; import { AppError } from '@agent-device/kernel/errors'; -import type { Point, SnapshotNode, SnapshotState } from '@agent-device/kernel/snapshot'; +import type { + Point, + SnapshotNode, + SnapshotPreferredBackend, + SnapshotQualityVerdict, + SnapshotState, +} from '@agent-device/kernel/snapshot'; import { extractReadableText } from '@agent-device/capture-kit/text-surface'; import { buildSelectorChainForNode } from '@agent-device/selectors'; import { fakeCreateCommandSurface } from './command-surface-fake.ts'; +import { + captureAndStoreSnapshot, + type CommandSurfaceMembers, +} from './command-surface-override-fake.ts'; /** * A command surface that answers the touch actions the way the real command family does at the two @@ -37,9 +47,12 @@ import { fakeCreateCommandSurface } from './command-surface-fake.ts'; * * Target resolution is deliberately the smallest rule that lets a dispatch happen: a point is * itself, a `@ref` is the centre of the node the session's authorized frame (or latest - * observation) names, and a selector is the first node of one fresh capture that matches one of its `||` alternatives - * (each a conjunction of `key=value` terms). Promotion, occlusion, off-screen and bounds guards, label recovery, and - * `--verify` evidence are command-family behavior and are asserted beside that code. + * observation) names, and a selector is the first node of one fresh capture, written back as the + * session's stored snapshot, that a `matchSelector` rule names (by default one of its `||` + * alternatives, each a conjunction of `key=value` terms). A target that does not resolve refuses + * with the typed `ref_not_found` / `selector_not_found` reason and `dispatched: 'no'`. Promotion, + * occlusion, off-screen and bounds guards, label recovery, and `--verify` evidence are + * command-family behavior and are asserted beside that code. * * Values the fake supplies itself rather than reading from a backend: an `is` read always passes * (`pass: true`, `matches: 1`); selector reads (`get`, `is`) capture once with @@ -65,25 +78,77 @@ type CommandOptions = Readonly<{ preresolvedTarget?: PreresolvedInteractionTarget; }>; -export const touchCreateCommandSurface: CreateDaemonCommandSurface = async (config) => { - touchSurfaceConfigs.push(config); - const inert = await fakeCreateCommandSurface(config); - return { - ...inert, - signal: config.signal, - selectors: touchSelectors(config, inert), - interactions: touchInteractions(config, inert), - }; +export type TouchSurfaceOptions = { + /** + * Which node a selector expression names. The default reads `||` alternatives of `key=value` + * terms; a suite that needs another selector shape states its own matcher. + */ + matchSelector?: (nodes: SnapshotNode[], selector: string) => SnapshotNode | undefined; + /** + * The post-action observation a `--settle` touch owes. It runs after the touch with the settle + * parameters the daemon forwarded, the pre-action tree, and a capture taken through the config's + * backend and stored as the session snapshot; its answer rides on the result. Without it the + * touch answers a settled observation of its own. + */ + observeSettle?: (params: { + settle: SettleParams; + baseline: SnapshotNode[] | undefined; + /** The quality verdict of the tree the touch resolved against, when it published one. */ + baselineQuality: SnapshotQualityVerdict | undefined; + capture: (options?: { + signal?: AbortSignal; + preferredBackend?: SnapshotPreferredBackend; + }) => Promise; + }) => Promise; + /** Members the suite answers itself, layered over the touch actions (selector reads, gestures). */ + members?: (config: AgentDeviceRuntimeConfig) => CommandSurfaceMembers; }; +type Rules = Readonly<{ + matchSelector: NonNullable; + observeSettle: TouchSurfaceOptions['observeSettle']; +}>; + +export function createTouchCommandSurface( + surfaceOptions: TouchSurfaceOptions = {}, +): CreateDaemonCommandSurface { + const rules: Rules = { + matchSelector: surfaceOptions.matchSelector ?? matchesSelector, + observeSettle: surfaceOptions.observeSettle, + }; + return async (config) => { + touchSurfaceConfigs.push(config); + const inert = await fakeCreateCommandSurface(config); + const own = surfaceOptions.members?.(config) ?? {}; + const selectors = touchSelectors(config, inert, rules); + const interactions = touchInteractions(config, inert, rules); + return { + ...inert, + signal: config.signal, + capture: { ...inert.capture, ...own.capture }, + selectors: { ...selectors, ...own.selectors }, + interactions: { ...interactions, ...own.interactions }, + }; + }; +} + +export const touchCreateCommandSurface: CreateDaemonCommandSurface = createTouchCommandSurface(); + function touchSelectors( config: AgentDeviceRuntimeConfig, inert: DaemonCommandSurface, + rules: Rules, ): DaemonCommandSurface['selectors'] { return { ...inert.selectors, get: async (options) => { - const resolved = await resolveTarget(config, options.target, options, READ_CAPTURE_OPTIONS); + const resolved = await resolveTarget( + config, + rules, + options.target, + options, + READ_CAPTURE_OPTIONS, + ); if (resolved.kind === 'point' || !resolved.node) { throw new AppError('COMMAND_FAILED', 'get target did not resolve to a node'); } @@ -101,6 +166,7 @@ function touchSelectors( is: async (options) => { const resolved = await resolveTarget( config, + rules, { kind: 'selector', selector: options.selector }, options, READ_CAPTURE_OPTIONS, @@ -130,29 +196,30 @@ async function readNodeText( function touchInteractions( config: AgentDeviceRuntimeConfig, inert: DaemonCommandSurface, + rules: Rules, ): DaemonCommandSurface['interactions'] { const press = async ( target: InteractionTarget, options: CommandOptions & { button?: unknown; [key: string]: unknown } = {}, ) => { - const resolved = await resolveTarget(config, target, options, undefined, 'tapTarget'); + const resolved = await resolveTarget(config, rules, target, options, undefined, 'tapTarget'); const backendResult = await dispatch(resolved, () => dispatchTap(config, resolved, options)); - return await finish(config, resolved, options, { backendResult }); + return await finish(config, rules, resolved, options, { backendResult }); }; return { ...inert.interactions, click: press, press, - gesture: async (options) => await dragOrInert(config, inert, options), + gesture: async (options) => await dragOrInert(config, rules, inert, options), fill: async (target, text, options = {}) => { - const resolved = await resolveTarget(config, target, options, undefined, 'fillTarget'); + const resolved = await resolveTarget(config, rules, target, options, undefined, 'fillTarget'); const backendResult = await dispatch(resolved, () => dispatchFill(config, resolved, text, options), ); - return await finish(config, resolved, options, { backendResult, text }); + return await finish(config, rules, resolved, options, { backendResult, text }); }, longPress: async (target, options = {}) => { - const resolved = await resolveTarget(config, target, options); + const resolved = await resolveTarget(config, rules, target, options); const backendResult = await dispatch(resolved, () => backendOperation(config.backend.longPress?.bind(config.backend))( context(config, options), @@ -160,17 +227,24 @@ function touchInteractions( { durationMs: options.durationMs }, ), ); - return await finish(config, resolved, options, { + return await finish(config, rules, resolved, options, { backendResult, durationMs: options.durationMs, }); }, hover: async (target, options = {}) => { - const resolved = await resolveTarget(config, target, options, undefined, 'hoverTarget'); + const resolved = await resolveTarget( + config, + rules, + target, + options, + undefined, + 'hoverTarget', + ); const backendResult = await dispatch(resolved, () => dispatchHover(config, resolved, options), ); - return await finish(config, resolved, options, { backendResult }); + return await finish(config, rules, resolved, options, { backendResult }); }, }; } @@ -182,6 +256,7 @@ function touchInteractions( */ async function dragOrInert( config: AgentDeviceRuntimeConfig, + rules: Rules, inert: DaemonCommandSurface, options: Parameters[0], ) { @@ -194,6 +269,7 @@ async function dragOrInert( async (expression) => await resolveTarget( config, + rules, expression.startsWith('@') ? { kind: 'ref', ref: expression } : { kind: 'selector', selector: expression }, @@ -257,8 +333,12 @@ async function dragOrInert( /** What a read (`get`, `is`) asks its capture for: the whole tree, with rects. */ const READ_CAPTURE_OPTIONS = { interactiveOnly: false, includeRects: true } as const; +/** The quality verdict of the tree each resolution was read from, when it published one. */ +const resolvedTreeQuality = new WeakMap(); + async function resolveTarget( config: AgentDeviceRuntimeConfig, + rules: Rules, target: InteractionTarget, options: CommandOptions, captureOptions: BackendSnapshotOptions = { interactiveOnly: true }, @@ -271,44 +351,81 @@ async function resolveTarget( if (nativeRefRoute && config.backend[nativeRefRoute]) return { kind: 'ref', target }; const record = await config.sessions?.get(options.session ?? 'default'); const tree = record?.refFrameSnapshot ?? record?.snapshot; - const node = tree?.nodes.find((candidate) => `@${candidate.ref}` === refBody(target.ref)); - if (!tree || !node) throw new AppError('COMMAND_FAILED', `Ref ${target.ref} not found`); - return resolvedFromNode(config, target, node, tree); + const node = tree?.nodes.find((candidate) => candidate.ref === refBody(target.ref)); + if (!tree || !node) { + throw new AppError('COMMAND_FAILED', `Ref ${target.ref} not found`, { + reason: 'ref_not_found', + dispatched: 'no', + }); + } + return resolvedFromNode(config, target, node, tree, chainAction(nativeRefRoute)); } - const captured = await config.backend.captureSnapshot?.(context(config, options), captureOptions); - const tree = captured?.snapshot; - const node = tree?.nodes.find((candidate) => matchesSelector(candidate, target.selector)); - if (!tree || !node) { - throw new AppError('COMMAND_FAILED', `Selector ${target.selector} did not match`); + const tree = await captureAndStoreSnapshot(config, { + session: options.session, + requestId: options.requestId, + signal: options.signal, + includeRects: true, + ...captureOptions, + }); + const node = rules.matchSelector(tree.nodes, target.selector); + if (!node) { + throw new AppError('COMMAND_FAILED', `Selector did not match: ${target.selector}`, { + reason: 'selector_not_found', + dispatched: 'no', + }); } - return { - kind: 'selector', - target: { kind: 'selector', selector: target.selector }, + const resolved = { + kind: 'selector' as const, + target: { kind: 'selector' as const, selector: target.selector }, point: centerOf(node) as Point, node, - selectorChain: buildSelectorChainForNode(node, config.backend.platform), + selectorChain: selectorChainFor(config, node, tree.nodes, chainAction(nativeRefRoute)), preAction: { nodes: tree.nodes }, }; + resolvedTreeQuality.set(resolved, tree.snapshotQuality); + return resolved; +} + +function chainAction( + route: 'tapTarget' | 'fillTarget' | 'hoverTarget' | undefined, +): 'click' | 'fill' | undefined { + if (route === 'tapTarget') return 'click'; + if (route === 'fillTarget') return 'fill'; + return undefined; +} + +function selectorChainFor( + config: AgentDeviceRuntimeConfig, + node: SnapshotNode, + nodes: SnapshotNode[], + action: 'click' | 'fill' | undefined, +): string[] { + return action + ? buildSelectorChainForNode(node, config.backend.platform, { action, nodes }) + : buildSelectorChainForNode(node, config.backend.platform); } function resolvedFromNode( config: AgentDeviceRuntimeConfig, target: Extract, node: SnapshotNode, - tree: Pick, + tree: Pick, + action?: 'click' | 'fill', ): ResolvedInteractionTarget { - return { - kind: 'ref', - target: { kind: 'ref', ref: target.ref }, + const resolved = { + kind: 'ref' as const, + target: { kind: 'ref' as const, ref: target.ref }, point: centerOf(node), node, - selectorChain: buildSelectorChainForNode(node, config.backend.platform), + selectorChain: selectorChainFor(config, node, tree.nodes, action), preAction: { nodes: tree.nodes }, }; + resolvedTreeQuality.set(resolved, tree.snapshotQuality); + return resolved; } function refBody(ref: string): string { - return ref.startsWith('@') ? ref : `@${ref}`; + return ref.replace(/^@/, '').replace(/~s\d+$/, ''); } function centerOf(node: SnapshotNode): Point | undefined { @@ -318,7 +435,11 @@ function centerOf(node: SnapshotNode): Point | undefined { } /** A selector chain: `||` alternatives, each a conjunction of `key=value` terms. */ -function matchesSelector(node: SnapshotNode, selector: string): boolean { +function matchesSelector(nodes: SnapshotNode[], selector: string): SnapshotNode | undefined { + return nodes.find((node) => nodeMatchesSelector(node, selector)); +} + +function nodeMatchesSelector(node: SnapshotNode, selector: string): boolean { const actual: Record = { id: node.identifier, label: node.label, @@ -427,12 +548,15 @@ async function dispatchHover( async function finish>( config: AgentDeviceRuntimeConfig, + rules: Rules, resolved: ResolvedInteractionTarget, options: CommandOptions, extra: TExtra, ) { const evidence = options.verify ? await verifyEvidence(config, resolved, options) : undefined; - const settle = options.settle ? await settleObservation(config, resolved, options) : undefined; + const settle = options.settle + ? await settleObservation(config, rules, resolved, options, options.settle) + : undefined; return { ...resolved, ...extra, @@ -461,9 +585,26 @@ async function verifyEvidence( async function settleObservation( config: AgentDeviceRuntimeConfig, + rules: Rules, resolved: ResolvedInteractionTarget, options: CommandOptions, + settle: SettleParams, ): Promise { + if (rules.observeSettle) { + return await rules.observeSettle({ + settle, + baseline: resolved.kind === 'point' ? undefined : resolved.preAction?.nodes, + baselineQuality: resolvedTreeQuality.get(resolved), + capture: (capture) => + captureAndStoreSnapshot(config, { + session: options.session, + requestId: options.requestId, + signal: capture?.signal ?? options.signal, + preferredBackend: capture?.preferredBackend, + includeRects: true, + }), + }); + } const captures = resolved.kind === 'point' ? 2 : 1; for (let capture = 0; capture < captures; capture += 1) { await config.backend.captureSnapshot?.(context(config, options), { interactiveOnly: true }); diff --git a/packages/daemon/src/__tests__/support/touch-forwarding-surface-fake.ts b/packages/daemon/src/__tests__/support/touch-forwarding-surface-fake.ts deleted file mode 100644 index f1694f3e37..0000000000 --- a/packages/daemon/src/__tests__/support/touch-forwarding-surface-fake.ts +++ /dev/null @@ -1,228 +0,0 @@ -import type { CreateDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; -import type { - FillCommandResult, - InteractionTarget, - PressCommandResult, - SettleObservation, - SettleParams, -} from '@agent-device/contracts/interaction'; -import { buildSelectorChainForNode } from '@agent-device/selectors'; -import { AppError } from '@agent-device/kernel/errors'; -import { - centerOfRect, - type Point, - type SnapshotNode, - type SnapshotPreferredBackend, - type SnapshotQualityVerdict, - type SnapshotState, -} from '@agent-device/kernel/snapshot'; -import { - captureAndStoreSnapshot, - commandSurfaceWithMembers, - type CommandSurfaceMembers, -} from './command-surface-override-fake.ts'; -import type { AgentDeviceRuntimeConfig } from '@agent-device/contracts/runtime-contract'; - -type TouchResolution = { - point: Point; - target: InteractionTarget; - node?: SnapshotNode; - nodes?: SnapshotNode[]; - selectorChain?: string[]; - snapshotQuality?: SnapshotQualityVerdict; -}; - -export type TouchForwardingSurfaceOptions = { - /** - * Which node a selector expression names. The default reads `label="X"` / `label=X` and - * `id="X"` / `id=X`; a suite that needs another selector shape states its own matcher. - */ - matchSelector?: (nodes: SnapshotNode[], selector: string) => SnapshotNode | undefined; - /** - * The post-action observation a `--settle` touch owes. It runs after the touch with the - * settle parameters the daemon forwarded, the pre-action tree, and a capture taken through the - * config's backend and stored as the session snapshot; its answer rides on the result. - */ - observeSettle?: (params: { - settle: SettleParams; - baseline: SnapshotNode[] | undefined; - /** The quality verdict of the tree the touch resolved against, when it published one. */ - baselineQuality: SnapshotQualityVerdict | undefined; - capture: (options?: { - signal?: AbortSignal; - preferredBackend?: SnapshotPreferredBackend; - }) => Promise; - }) => Promise; - /** Members the suite answers itself, layered over the touch actions (selector reads, gestures). */ - members?: (config: AgentDeviceRuntimeConfig) => CommandSurfaceMembers; -}; - -function defaultMatchSelector(nodes: SnapshotNode[], selector: string): SnapshotNode | undefined { - const parsed = /^(label|id)="?([^"]*)"?$/.exec(selector.trim()); - if (!parsed) return undefined; - const [, key, value] = parsed; - return nodes.find((node) => (key === 'id' ? node.identifier : node.label) === value); -} - -function refBody(ref: string): string { - return ref.replace(/^@/, '').replace(/~s\d+$/, ''); -} - -/** - * A command surface whose touch actions do what the daemon depends on the real command family - * to do, and no more: locate the target (a stored-tree lookup for a `@ref`, one interactive - * capture through the config's backend for a selector), dispatch the touch through the - * config's backend, and answer with the resolved target and the tree it was read from. Selector - * disambiguation, hittability, the moving-target gate, and every other command-family decision - * are out of scope: the suites that use this surface pin what the daemon does around them. - */ -export function touchForwardingCreateCommandSurface( - options: TouchForwardingSurfaceOptions = {}, -): CreateDaemonCommandSurface { - const matchSelector = options.matchSelector ?? defaultMatchSelector; - return commandSurfaceWithMembers((config) => { - const locate = async ( - target: InteractionTarget, - context: { session?: string; requestId?: string; signal?: AbortSignal }, - action: 'click' | 'fill', - ): Promise => { - const chainFor = (node: SnapshotNode, nodes: SnapshotNode[]) => - buildSelectorChainForNode(node, config.backend.platform, { action, nodes }); - if (target.kind === 'point') return { point: { x: target.x, y: target.y }, target }; - if (target.kind === 'ref') { - const record = await config.sessions?.get(context.session ?? 'default'); - const tree = record?.refFrameSnapshot ?? record?.snapshot; - const nodes = tree?.nodes ?? []; - const node = nodes.find((candidate) => candidate.ref === refBody(target.ref)); - if (!node?.rect) { - throw new AppError('COMMAND_FAILED', `Ref ${target.ref} not found`, { - reason: 'ref_not_found', - dispatched: 'no', - }); - } - return { - point: centerOfRect(node.rect), - target, - node, - nodes, - selectorChain: chainFor(node, nodes), - snapshotQuality: tree?.snapshotQuality, - }; - } - const { nodes, snapshotQuality } = await captureAndStoreSnapshot(config, { - ...context, - interactiveOnly: true, - includeRects: true, - }); - const node = matchSelector(nodes, target.selector); - if (node?.rect) { - return { - point: centerOfRect(node.rect), - target, - node, - nodes, - selectorChain: chainFor(node, nodes), - snapshotQuality, - }; - } - throw new AppError('COMMAND_FAILED', `Selector did not match: ${target.selector}`, { - reason: 'selector_not_found', - dispatched: 'no', - }); - }; - - const resolvedTarget = (resolution: TouchResolution) => { - const { target, point, node, nodes, selectorChain } = resolution; - const preAction = nodes ? { nodes } : undefined; - if (target.kind === 'point') return { kind: 'point' as const, point, preAction }; - if (target.kind === 'ref') { - return { kind: 'ref' as const, point, target, node, selectorChain, preAction }; - } - return { - kind: 'selector' as const, - point, - target, - node: node as SnapshotNode, - selectorChain: selectorChain ?? [], - preAction, - }; - }; - - const observe = async ( - context: { session?: string; requestId?: string; signal?: AbortSignal }, - resolution: TouchResolution, - settle: SettleParams | undefined, - ): Promise => { - if (!settle || !options.observeSettle) return undefined; - return await options.observeSettle({ - settle, - baseline: resolution.nodes, - baselineQuality: resolution.snapshotQuality, - capture: (capture) => - captureAndStoreSnapshot(config, { - ...context, - signal: capture?.signal ?? context.signal, - preferredBackend: capture?.preferredBackend, - includeRects: true, - }), - }); - }; - - const press = async ( - target: InteractionTarget, - pressOptions?: { - session?: string; - requestId?: string; - signal?: AbortSignal; - settle?: SettleParams; - }, - ): Promise => { - const context = { - session: pressOptions?.session, - requestId: pressOptions?.requestId, - signal: pressOptions?.signal, - }; - const resolution = await locate(target, context, 'click'); - const backendResult = await config.backend.tap?.(context, resolution.point); - const settle = await observe(context, resolution, pressOptions?.settle); - return { - ...resolvedTarget(resolution), - ...(backendResult ? { backendResult } : {}), - ...(settle ? { settle } : {}), - }; - }; - - const fill = async ( - target: InteractionTarget, - text: string, - fillOptions?: { - session?: string; - requestId?: string; - signal?: AbortSignal; - settle?: SettleParams; - }, - ): Promise => { - const context = { - session: fillOptions?.session, - requestId: fillOptions?.requestId, - signal: fillOptions?.signal, - }; - const resolution = await locate(target, context, 'fill'); - const backendResult = await config.backend.fill?.(context, resolution.point, text); - const settle = await observe(context, resolution, fillOptions?.settle); - return { - ...resolvedTarget(resolution), - text, - ...(backendResult ? { backendResult } : {}), - ...(settle ? { settle } : {}), - }; - }; - - const own = options.members?.(config) ?? {}; - return { - capture: own.capture, - selectors: own.selectors, - interactions: { press, click: press, fill, ...own.interactions }, - }; - }); -} diff --git a/packages/daemon/src/execution/interaction/internal/interaction-capture-disclosure.test.ts b/packages/daemon/src/execution/interaction/internal/interaction-capture-disclosure.test.ts index d6cacf552a..da431d302a 100644 --- a/packages/daemon/src/execution/interaction/internal/interaction-capture-disclosure.test.ts +++ b/packages/daemon/src/execution/interaction/internal/interaction-capture-disclosure.test.ts @@ -14,7 +14,7 @@ import { legacyDispatchCapture } from '../../../__tests__/support/legacy-snapsho import { markDeferredInteractionOutcome } from '../../../core/deferred-interaction-outcome.ts'; import { formatPostGestureOutcomeWarning } from '@agent-device/capture-kit/post-gesture-stability'; import { installLegacyCaptureInteractorResolution } from '../../../__tests__/support/legacy-capture-interactor-resolution.ts'; -import { touchForwardingCreateCommandSurface } from '../../../__tests__/support/touch-forwarding-surface-fake.ts'; +import { createTouchCommandSurface } from '../../../__tests__/support/touch-command-surface-fake.ts'; installLegacyCaptureInteractorResolution(); @@ -56,7 +56,7 @@ async function pressSelector(params: { const sessionStore = makeSessionStore(); sessionStore.publish(params.sessionName, makeSession(params.sessionName)); const response = await handleInteractionCommands({ - createCommandSurface: touchForwardingCreateCommandSurface(), + createCommandSurface: createTouchCommandSurface(), req: { token: 't', session: params.sessionName, @@ -109,7 +109,7 @@ test('a press that consumes no capture is not disclosed against an older tree', sessionStore.publish(sessionName, session); const response = await handleInteractionCommands({ - createCommandSurface: touchForwardingCreateCommandSurface(), + createCommandSurface: createTouchCommandSurface(), req: { token: 't', session: sessionName, @@ -169,7 +169,7 @@ async function pressAfterUnsettledScroll(selector: string) { vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }); let done = false; const pending = handleInteractionCommands({ - createCommandSurface: touchForwardingCreateCommandSurface(), + createCommandSurface: createTouchCommandSurface(), req: { token: 't', session: 'default', command: 'press', positionals: [selector], flags: {} }, sessionName: 'default', sessionStore, diff --git a/packages/daemon/src/execution/interaction/internal/interaction-common.test.ts b/packages/daemon/src/execution/interaction/internal/interaction-common.test.ts index 7e3a513953..8d14d288e8 100644 --- a/packages/daemon/src/execution/interaction/internal/interaction-common.test.ts +++ b/packages/daemon/src/execution/interaction/internal/interaction-common.test.ts @@ -16,7 +16,7 @@ import { mockFillPoint, resetGetRuntimeFixture, } from '../../__tests__/interaction-get-runtime-fixture.ts'; -import { touchForwardingCreateCommandSurface } from '../../../__tests__/support/touch-forwarding-surface-fake.ts'; +import { createTouchCommandSurface } from '../../../__tests__/support/touch-command-surface-fake.ts'; const contextFromFlags = (_flags: CommandFlags | undefined) => ({}); @@ -163,7 +163,7 @@ test('parameterized fill scrubs concatenated backend values and object keys thro ); const response = await handleInteractionCommands({ - createCommandSurface: touchForwardingCreateCommandSurface(), + createCommandSurface: createTouchCommandSurface(), req: { token: 't', session: sessionName, @@ -221,7 +221,7 @@ test('parameterized fill collapses whitespace-only backend echoes through the ha }); const response = await handleInteractionCommands({ - createCommandSurface: touchForwardingCreateCommandSurface(), + createCommandSurface: createTouchCommandSurface(), req: { token: 't', session: sessionName, @@ -327,7 +327,7 @@ test('a fill on a @ref acts on the node of the authorized ref frame, not on a ne mockFillPoint.mockResolvedValue({ filled: true }); const response = await handleInteractionCommands({ - createCommandSurface: touchForwardingCreateCommandSurface(), + createCommandSurface: createTouchCommandSurface(), req: { token: 't', session: sessionName, diff --git a/packages/daemon/src/execution/interaction/internal/interaction-dispatch-disclosure.test.ts b/packages/daemon/src/execution/interaction/internal/interaction-dispatch-disclosure.test.ts index d885fcb40c..90eefb5662 100644 --- a/packages/daemon/src/execution/interaction/internal/interaction-dispatch-disclosure.test.ts +++ b/packages/daemon/src/execution/interaction/internal/interaction-dispatch-disclosure.test.ts @@ -37,7 +37,7 @@ import { makeSession, makeStaleRefSession, } from './__tests__/interaction-touch-fixtures.ts'; -import { touchForwardingCreateCommandSurface } from '../../../__tests__/support/touch-forwarding-surface-fake.ts'; +import { createTouchCommandSurface } from '../../../__tests__/support/touch-command-surface-fake.ts'; // contracts/fixtures/dispatch-disclosure.json, daemon rows: each drives a `press`, `fill`, `get`, // or `swipe` through the daemon interaction handler, inside the router's dispatch seam, over a @@ -62,7 +62,7 @@ const GESTURE_PLAN: GesturePlan = { ], }; -const createCommandSurface = touchForwardingCreateCommandSurface({ +const createCommandSurface = createTouchCommandSurface({ members: (config) => ({ selectors: { get: async () => { diff --git a/packages/daemon/src/execution/interaction/internal/interaction-settle-private-ax-route.test.ts b/packages/daemon/src/execution/interaction/internal/interaction-settle-private-ax-route.test.ts index 9adbbd636b..1cff0e1c02 100644 --- a/packages/daemon/src/execution/interaction/internal/interaction-settle-private-ax-route.test.ts +++ b/packages/daemon/src/execution/interaction/internal/interaction-settle-private-ax-route.test.ts @@ -12,7 +12,7 @@ import { contextFromFlags as buildDaemonContext } from '../../../core/context.ts import { installInteractorResolution } from '../../../core/interactor-resolution.ts'; import { handleInteractionCommands } from '../index.ts'; import { getRuntimeBindings } from '../../__tests__/interaction-get-runtime-fixture.ts'; -import { touchForwardingCreateCommandSurface } from '../../../__tests__/support/touch-forwarding-surface-fake.ts'; +import { createTouchCommandSurface } from '../../../__tests__/support/touch-command-surface-fake.ts'; // The settle loop pins `private-ax` on its captures once the observation reports that producer; the // daemon must carry that preference through its capture path to the interactor, which is what the @@ -62,7 +62,7 @@ beforeEach(() => { }); /** The pin the settle loop applies: every settle capture asks for `private-ax` once it is established. */ -const pinningSurface = touchForwardingCreateCommandSurface({ +const pinningSurface = createTouchCommandSurface({ observeSettle: async ({ settle, baselineQuality, capture }): Promise => { let pinned = baselineQuality?.backend === 'private-ax'; for (let captures = 0; captures < 3; captures += 1) { diff --git a/packages/daemon/src/execution/interaction/internal/interaction-settle.test.ts b/packages/daemon/src/execution/interaction/internal/interaction-settle.test.ts index 2cbfe26249..4d77ad2a61 100644 --- a/packages/daemon/src/execution/interaction/internal/interaction-settle.test.ts +++ b/packages/daemon/src/execution/interaction/internal/interaction-settle.test.ts @@ -27,7 +27,7 @@ import { } from '../../__tests__/interaction-get-runtime-fixture.ts'; import { daemonPlatformServicesFixture } from '../../../__tests__/support/platform-services-fixture.ts'; import { installLegacyCaptureInteractorResolution } from '../../../__tests__/support/legacy-capture-interactor-resolution.ts'; -import { touchForwardingCreateCommandSurface } from '../../../__tests__/support/touch-forwarding-surface-fake.ts'; +import { createTouchCommandSurface } from '../../../__tests__/support/touch-command-surface-fake.ts'; import type { SettleObservation } from '@agent-device/contracts/interaction'; import type { SnapshotNode } from '@agent-device/kernel/snapshot'; @@ -145,11 +145,11 @@ function settledDiff(baseline: SnapshotNode[] | undefined, settled: SnapshotNode } /** - * The touch-forwarding surface with the post-action observation `--settle` owes: it captures the + * The touch command surface with the post-action observation `--settle` owes: it captures the * settled tree through the config's backend under the settle budget's deadline and answers with the * diff, or with the unavailable/stalled verdict the command family reports when that capture fails. */ -const settlingSurface = touchForwardingCreateCommandSurface({ +const settlingSurface = createTouchCommandSurface({ observeSettle: async ({ settle, baseline, capture }): Promise => { const quietMs = settle.quietMs ?? 500; const timeoutMs = settle.timeoutMs ?? 10_000; diff --git a/packages/daemon/src/execution/interaction/internal/interaction-target-evidence.test.ts b/packages/daemon/src/execution/interaction/internal/interaction-target-evidence.test.ts index ee50b93c73..1d3048cfdc 100644 --- a/packages/daemon/src/execution/interaction/internal/interaction-target-evidence.test.ts +++ b/packages/daemon/src/execution/interaction/internal/interaction-target-evidence.test.ts @@ -16,7 +16,7 @@ import { SAVE_BUTTON_EVIDENCE, makeSessionWithSaveButtonSnapshot, } from '../../../__tests__/support/target-evidence-fixture.ts'; -import { touchForwardingCreateCommandSurface } from '../../../__tests__/support/touch-forwarding-surface-fake.ts'; +import { createTouchCommandSurface } from '../../../__tests__/support/touch-command-surface-fake.ts'; import { getRuntimeBindings, resetGetRuntimeFixture, @@ -44,7 +44,7 @@ async function runCommand( command: string, positionals: string[], flags: CommandFlags = {}, - createCommandSurface = touchForwardingCreateCommandSurface(), + createCommandSurface = createTouchCommandSurface(), ) { return await handleInteractionCommands({ createCommandSurface, @@ -138,7 +138,7 @@ const IDENTITY_EMPTY_ROW_NODES: RawSnapshotNode[] = [ * The command family's answer for a press on that container: the response describes the dispatched * container, and the labeled descendant rides the recording-only `recordingTarget` side channel. */ -const containerPressSurface = touchForwardingCreateCommandSurface({ +const containerPressSurface = createTouchCommandSurface({ members: (config) => ({ interactions: { press: async (target, options) => {