Skip to content

Commit 8aaeade

Browse files
committed
fix: reconcile Maestro runtime with merged contracts
1 parent a1ad265 commit 8aaeade

8 files changed

Lines changed: 27 additions & 37 deletions

src/compat/maestro/__tests__/runtime-target-policy.test.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,9 @@ test('typed Maestro text selectors match visible text and state without expressi
1414
]).nodes[0]!;
1515

1616
expect(
17-
matchesMaestroTypedSelector(
18-
node,
19-
{ text: '^Subtotal.*', enabled: true, selected: true },
20-
),
17+
matchesMaestroTypedSelector(node, { text: '^Subtotal.*', enabled: true, selected: true }),
2118
).toBe(true);
22-
expect(matchesMaestroTypedSelector(node, { text: 'Subtotal', selected: false })).toBe(
23-
false,
24-
);
19+
expect(matchesMaestroTypedSelector(node, { text: 'Subtotal', selected: false })).toBe(false);
2520
});
2621

2722
test('typed Maestro id and label selectors keep their primary field semantics', () => {

src/compat/maestro/__tests__/runtime-target-ranking.test.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,7 @@ test('target ranking promotes actionable controls over matching static text', ()
1919
]);
2020

2121
expect(
22-
selectMaestroSnapshotMatch(
23-
snapshot.nodes,
24-
snapshot.nodes,
25-
undefined,
26-
'Save',
27-
IOS_TAB_FRAME,
28-
),
22+
selectMaestroSnapshotMatch(snapshot.nodes, snapshot.nodes, undefined, 'Save', IOS_TAB_FRAME),
2923
).toMatchObject({ node: { index: 2 } });
3024
});
3125

src/compat/maestro/daemon-runtime-port-support.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import path from 'node:path';
2-
import type { NormalizedGestureInput } from '../../contracts/gesture-normalization.ts';
2+
import type { GestureSemanticInput } from '../../contracts/gesture-plan-types.ts';
33
import type { CommandFlags } from '../../core/dispatch.ts';
44
import type {
55
DaemonInvokeFn,
@@ -68,7 +68,7 @@ export function launchArgumentValues(
6868
}
6969

7070
export function publicGestureRequest(
71-
input: NormalizedGestureInput,
71+
input: GestureSemanticInput,
7272
context: MaestroRuntimeOperationContext,
7373
): { command: 'gesture' | 'swipe'; input: Record<string, unknown> } {
7474
const endpoints = endpointGesture(input);
@@ -210,7 +210,7 @@ function daemonResponseError(response: Extract<DaemonResponse, { ok: false }>):
210210
return new AppError(error.code, error.message, Object.keys(details).length ? details : undefined);
211211
}
212212

213-
function endpointGesture(input: NormalizedGestureInput): Record<string, unknown> | undefined {
213+
function endpointGesture(input: GestureSemanticInput): Record<string, unknown> | undefined {
214214
if (input.intent === 'fling' && 'from' in input) return { from: input.from, to: input.to };
215215
if (input.intent === 'pan' && !('preset' in input)) {
216216
return {

src/compat/maestro/runtime-port-geometry.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import { AppError } from '../../kernel/errors.ts';
2-
import {
3-
normalizePublicSwipeMotion,
4-
type NormalizedGestureInput,
5-
} from '../../contracts/gesture-normalization.ts';
6-
import { clampToRange } from '../../contracts/scroll-gesture.ts';
7-
import { buildInPageSwipeGesturePlan } from '../../contracts/scroll-gesture.ts';
2+
import { normalizePublicSwipeMotion } from '../../contracts/gesture-normalization.ts';
3+
import type { GestureSemanticInput } from '../../contracts/gesture-plan-types.ts';
4+
import { buildInPageSwipeGesturePlan, clampToRange } from '../../contracts/scroll-gesture.ts';
85
import { pointInsideRect } from '../../utils/rect-center.ts';
96
import type { MaestroRuntimeRequest } from './engine-types.ts';
107
import type { MaestroCoordinate, MaestroDirection, MaestroSwipeGesture } from './program-ir.ts';
@@ -109,14 +106,14 @@ function normalizedSwipeFromEndpoints(
109106
start: { x: number; y: number },
110107
end: { x: number; y: number },
111108
durationMs: number | undefined,
112-
): NormalizedGestureInput {
109+
): GestureSemanticInput {
113110
return normalizePublicSwipeMotion({ from: start, to: end, durationMs }).gesture;
114111
}
115112

116113
function normalizedScreenHorizontalSwipe(
117114
direction: Extract<MaestroDirection, 'left' | 'right'>,
118115
durationMs: number | undefined,
119-
): NormalizedGestureInput {
116+
): GestureSemanticInput {
120117
const preset = direction;
121118
return durationMs === undefined
122119
? { intent: 'fling', preset }
@@ -128,7 +125,7 @@ function normalizedScreenVerticalSwipe(
128125
start: { x: number; y: number },
129126
end: { x: number; y: number },
130127
durationMs: number | undefined,
131-
): NormalizedGestureInput {
128+
): GestureSemanticInput {
132129
if (durationMs !== undefined) return normalizedSwipeFromEndpoints(start, end, durationMs);
133130
return {
134131
intent: 'fling',

src/compat/maestro/runtime-port-types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { NormalizedGestureInput } from '../../contracts/gesture-normalization.ts';
1+
import type { GestureSemanticInput } from '../../contracts/gesture-plan-types.ts';
22
import type { Point, Rect } from '../../kernel/snapshot.ts';
33
import type {
44
MaestroDirection,
@@ -63,7 +63,7 @@ export type MaestroSwipeOperation = {
6363
/** The authored Maestro coordinate space and target mode, preserved for policy and diagnostics. */
6464
readonly authored: MaestroSwipeGesture;
6565
/** The normalized contract consumed by the shared input runtime. */
66-
readonly gesture: NormalizedGestureInput;
66+
readonly gesture: GestureSemanticInput;
6767
readonly target?: MaestroTargetResolution;
6868
readonly viewport?: Rect;
6969
};
@@ -117,7 +117,7 @@ export type MaestroRuntimeOperations = {
117117
readonly delay?: number;
118118
}>;
119119
readonly longPressOn: MaestroRuntimeOperation<{ readonly target: MaestroInputTarget }>;
120-
readonly gesture: MaestroRuntimeOperation<NormalizedGestureInput>;
120+
readonly gesture: MaestroRuntimeOperation<GestureSemanticInput>;
121121
readonly inputText: MaestroRuntimeOperation<{ readonly text: string; readonly label?: string }>;
122122
readonly eraseText: MaestroRuntimeOperation<{ readonly charactersToErase?: number }>;
123123
readonly pasteText: MaestroRuntimeOperation<{ readonly text: string }>;

src/daemon/handlers/__tests__/session-replay-vars.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1889,10 +1889,11 @@ test('runReplayScriptFile propagates Maestro runFlow.when runtime errors', async
18891889
assert.equal(response.error.code, 'REPLAY_DIVERGENCE');
18901890
assert.match(response.error.message, /fetch failed/);
18911891
const divergence = response.error.details?.divergence as
1892-
| { cause: { code: string; message: string } }
1892+
| { cause: { code: string; message: string }; repairHint: string }
18931893
| undefined;
18941894
assert.equal(divergence?.cause.code, 'UNKNOWN');
18951895
assert.match(divergence?.cause.message ?? '', /fetch failed/);
1896+
assert.equal(divergence?.repairHint, 'manual');
18961897
}
18971898
});
18981899

src/daemon/handlers/session-replay-maestro-failure.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import {
1818
buildDivergenceScreen,
1919
captureDivergenceObservation,
2020
collectReplayDivergenceSuggestions,
21+
toReplayRepairHintCapture,
2122
} from './session-replay-divergence.ts';
23+
import { computeReplayRepairHint } from './session-replay-repair-hint.ts';
2224
import {
2325
buildReplayDivergenceFailureResponseFromDescriptor,
2426
hoistReplayFailureCauseDiagnosticMeta,
@@ -104,6 +106,11 @@ export async function buildTypedMaestroFailureResponse(params: {
104106
planDigest: plan.digest,
105107
reason: resume.reason,
106108
},
109+
repairHint: computeReplayRepairHint({
110+
kind: 'action-failure',
111+
targetEvidence: diagnosticAction.targetEvidence,
112+
capture: toReplayRepairHintCapture(observation),
113+
}),
107114
};
108115
const bounded = boundReplayDivergenceForSession({
109116
sessionStore,
@@ -125,10 +132,7 @@ export async function buildTypedMaestroFailureResponse(params: {
125132
});
126133
}
127134

128-
function diagnosticActionForEvent(
129-
event: MaestroEngineEvent,
130-
req: DaemonRequest,
131-
): SessionAction {
135+
function diagnosticActionForEvent(event: MaestroEngineEvent, req: DaemonRequest): SessionAction {
132136
const progress = formatMaestroCommandProgress(event.command);
133137
const command = diagnosticCommand(event.command.kind);
134138
return {
@@ -158,9 +162,7 @@ function safeProgressPositionals(command: string, value: string | undefined): st
158162
return [value];
159163
}
160164

161-
function collectExpandedScrubVars(
162-
values: Readonly<Record<string, string>>,
163-
): ReplayVarScrubEntry[] {
165+
function collectExpandedScrubVars(values: Readonly<Record<string, string>>): ReplayVarScrubEntry[] {
164166
return Object.entries(values)
165167
.filter(([, value]) => value.length > 0)
166168
.map(([name, value]) => ({ name, value }))

src/daemon/handlers/session-replay-runtime.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fs from 'node:fs';
2+
import path from 'node:path';
23
import { parseReplayInput } from '../../compat/replay-input.ts';
34
import { asAppError } from '../../kernel/errors.ts';
45
import type { DaemonInvokeFn, DaemonRequest, DaemonResponse, SessionAction } from '../types.ts';

0 commit comments

Comments
 (0)