Repository navigation
refactor(replay-port): own the replay backend contract in contracts #3622
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
thymikee
wants to merge
4
commits into
main
Choose a base branch
from
refactor/maestro-replay-backend-plugin-shape
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+444
−348
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
8a48e28
refactor(replay-port): own the replay backend contract in contracts
thymikee 04f53b7
chore(gates): export the replay backend contract from contracts
thymikee 87b7517
fix(replay-port): keep the presence-only dispatch literals and docume…
thymikee e59514f
docs(maestro): restore the dispatch option docs on the presence-only …
thymikee File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,207 @@ | ||
| import type { DaemonError } from '@agent-device/kernel/errors'; | ||
| import type { DaemonResponse, SessionRuntimeHints } from '@agent-device/kernel/contracts'; | ||
| import type { DeviceInfo } from '@agent-device/kernel/device'; | ||
| import type { SnapshotNode, SnapshotState } from '@agent-device/kernel/snapshot'; | ||
| import type { DaemonWireRequest } from './daemon-wire-request.ts'; | ||
| import type { | ||
| ReplayDivergenceResume, | ||
| ReplayDivergenceSuggestionBasis, | ||
| } from './replay-divergence.ts'; | ||
| import type { ReplayDispatchOptions, ReplayObservationAuthorityBinder } from './replay.ts'; | ||
| import type { SessionAction } from './session-action.ts'; | ||
| import type { | ||
| SnapshotDiagnosticsState, | ||
| SnapshotDiagnosticsSummary, | ||
| } from './snapshot-diagnostics.ts'; | ||
|
|
||
| /** | ||
| * The replay backend contract (#3377): the session and command shapes a non-native replay engine | ||
| * is handed, the capabilities it implements, and the failure descriptor the daemon reports for a | ||
| * failed step. Everything here is type-only, so an engine can depend on this module without | ||
| * evaluating any host code. | ||
| */ | ||
|
|
||
| /** | ||
| * The slice of the daemon's live session record replay reads. The daemon passes its full | ||
| * `SessionState`; replay never names the record itself, so the port depends on these fields and | ||
| * nothing else the daemon keeps on a session. | ||
| */ | ||
| export type ReplaySessionState = Readonly<{ | ||
| name: string; | ||
| device: DeviceInfo; | ||
| appBundleId?: string; | ||
| actions: SessionAction[]; | ||
| trace?: Readonly<{ outPath: string }>; | ||
| /** Read for presence only: an active recording means replay video must not start another. */ | ||
| screenRecording?: object; | ||
| snapshotDiagnostics?: Pick<SnapshotDiagnosticsState, 'samples'>; | ||
| }>; | ||
|
|
||
| export type ReplaySessionStore = Readonly<{ | ||
| get: () => ReplaySessionState | undefined; | ||
| lookup: () => Readonly<{ address: string; session: ReplaySessionState }> | undefined; | ||
| getRuntimeHints: () => SessionRuntimeHints | undefined; | ||
| ensureSessionDir: () => string; | ||
| /** Rejects the request when its device/platform selectors contradict the session's binding. */ | ||
| assertSelectorMatches: (flags: DaemonWireRequest['flags']) => void; | ||
| /** | ||
| * The runtime hints an `open` dispatched from this session would run with: the request's own, | ||
| * the session's persisted ones, and the platform defaults the daemon owns. | ||
| */ | ||
| resolveOpenRuntimeHints: (params: { | ||
| request: DaemonWireRequest; | ||
| device?: DeviceInfo; | ||
| platform?: 'ios' | 'android'; | ||
| }) => SessionRuntimeHints | undefined; | ||
| }>; | ||
|
|
||
| /** | ||
| * The replay side of the ref-publication owner: the operational capture it reads, a binder for | ||
| * the daemon-side authority that records a capture and publishes exactly its own projection, and | ||
| * the daemon's snapshot capture over this session. The session-store pair the authority is drawn | ||
| * from stays in the daemon. | ||
| */ | ||
| export type ReplaySessionObservation = Readonly<{ | ||
| get: () => ReplaySessionState | undefined; | ||
| bindAuthority: (signal?: AbortSignal) => ReturnType<ReplayObservationAuthorityBinder> & | ||
| Readonly<{ | ||
| capture: (params: { | ||
| flags: DaemonWireRequest['flags']; | ||
| logPath: string; | ||
| }) => Promise<{ snapshot: SnapshotState }>; | ||
| }>; | ||
| }>; | ||
|
|
||
| export type ReplayDispatchRequest = DaemonWireRequest & | ||
| Readonly<{ dispatch?: ReplayDispatchOptions }>; | ||
|
|
||
| export type ReplayInvoke = (request: ReplayDispatchRequest) => Promise<DaemonResponse>; | ||
|
|
||
| /** | ||
| * The dispatch options a backend may ask the daemon to honor on a nested public command. A key | ||
| * a backend adds without a counterpart in `ReplayDispatchOptions` fails to compile here. | ||
| */ | ||
| export type ReplayBackendDispatchOptions = Pick< | ||
| ReplayDispatchOptions, | ||
| | 'closeAppOnly' | ||
| | 'killApp' | ||
| | 'observationOnly' | ||
| | 'gestureViewport' | ||
| | 'gestureExecutionProfile' | ||
| | 'settingsAppBundleId' | ||
| >; | ||
|
|
||
| /** One execution step a backend reports while an attempt runs. */ | ||
| export type ReplayBackendStep = Readonly<{ | ||
| index: number; | ||
| total: number; | ||
| command?: string; | ||
| value?: string; | ||
| }>; | ||
|
|
||
| export type ReplayBackendSession = Readonly<{ | ||
| /** The effective SessionStore key selected by request binding. */ | ||
| name: string; | ||
| logPath: string; | ||
| store: ReplaySessionStore; | ||
| /** Bound observation reads and writes used by the ref-publication owner and divergence capture. */ | ||
| observationStore: ReplaySessionObservation; | ||
| }>; | ||
|
|
||
| /** One replay run as a backend sees it: the prepared request, its session, and the dispatch port. */ | ||
| export type ReplayBackendCommand = Readonly<{ | ||
| request: DaemonWireRequest; | ||
| /** | ||
| * True when the request reached the daemon over its public HTTP surface only: flow scripts are | ||
| * then untrusted and `runScript` HTTP calls may not reach private addresses. Absent means a | ||
| * local caller, which trusts its own scripts. | ||
| */ | ||
| publicNetworkOnly?: true; | ||
| /** Dispatch options every request this command issues carries, before its own. */ | ||
| dispatch?: ReplayDispatchOptions; | ||
| session: ReplayBackendSession; | ||
| invoke: ReplayInvoke; | ||
| tracePath?: string; | ||
| onStep?: (step: ReplayBackendStep) => void; | ||
| }>; | ||
|
|
||
| /** What a backend reports about one source before any run: the shared scheduling vocabulary. */ | ||
| export type ReplaySourceInspection = Readonly<{ | ||
| /** The flow's authored title, when the format carries one. */ | ||
| title: string | undefined; | ||
| /** A static app target the entry resolves to, for advisory device binding. */ | ||
| appTarget: string | undefined; | ||
| }>; | ||
|
|
||
| /** Reads one flow file, or reports that it is unavailable (#1802 readers). */ | ||
| export type ReplayOptionalSourceReader = (resolvedPath: string) => string | undefined; | ||
|
|
||
| export type ReplayBackendExportWarning = Readonly<{ | ||
| line: number; | ||
| action: string; | ||
| message: string; | ||
| }>; | ||
|
|
||
| export type ReplayBackendExportResult = Readonly<{ | ||
| yaml: string; | ||
| warnings: ReplayBackendExportWarning[]; | ||
| }>; | ||
|
|
||
| /** | ||
| * `TId` is the format grammar's backend vocabulary (`ReplayBackendId` in `@agent-device/ad-script`, | ||
| * which sits above this package), so the registry's keys and the `--maestro` flag's accepted | ||
| * values cannot drift apart. | ||
| */ | ||
| export type ReplayBackend<TId extends string = string> = Readonly<{ | ||
| id: TId; | ||
| /** Expands the entry flow's include closure into a path-to-text map (best effort, #1802). */ | ||
| collectSourceFiles(params: { | ||
| entryPath: string; | ||
| entrySource: string; | ||
| env?: Readonly<Record<string, string>>; | ||
| readSource: ReplayOptionalSourceReader; | ||
| }): Record<string, string>; | ||
| /** Reads title/app-target metadata from one source, without running it. Throws on an invalid flow. */ | ||
| inspectSource(source: string, sourcePath: string): ReplaySourceInspection; | ||
| /** Runs one replay through the backend's engine, owning no session or wire state. */ | ||
| runReplay(command: ReplayBackendCommand): Promise<DaemonResponse>; | ||
| /** Converts a native `.ad` action list into the backend's own script format. */ | ||
| exportReplayScript( | ||
| actions: SessionAction[], | ||
| options: { actionLines?: number[]; metadata?: { env?: Record<string, string> } }, | ||
| ): ReplayBackendExportResult; | ||
| }>; | ||
|
|
||
| /** One failed step of a backend run, in the vocabulary the divergence report publishes. */ | ||
| export type ReplayBackendActionFailure = Readonly<{ | ||
| error: DaemonError; | ||
| req: DaemonWireRequest; | ||
| sessionStore: ReplaySessionStore; | ||
| observationStore: ReplaySessionObservation; | ||
| logPath: string; | ||
| replayPath: string; | ||
| stepIndex: number; | ||
| source: Readonly<{ path?: string; line: number }>; | ||
| /** The authored step name, shown in the report and recorded as the failed action. */ | ||
| action: string; | ||
| /** The step's display label: its name plus its rendered argument. */ | ||
| actionLabel: string; | ||
| /** The `.ad`-shaped action the post-failure capture and suggestion ranking read. */ | ||
| captureAction: Readonly<{ | ||
| command: string; | ||
| positionals: readonly string[]; | ||
| flags: SessionAction['flags']; | ||
| }>; | ||
| /** Control-flow steps fail without suggestions; the post-failure screen is still captured. */ | ||
| isControl: boolean; | ||
| /** Values scrubbed from every published string. */ | ||
| redactions: readonly Readonly<{ name: string; value: string }>[]; | ||
| resume: ReplayDivergenceResume; | ||
| artifactPaths: readonly string[]; | ||
| warnings: readonly string[]; | ||
| snapshotDiagnostics?: SnapshotDiagnosticsSummary; | ||
| /** Candidate nodes the engine proposes for the failed step, before host ranking and bounding. */ | ||
| suggestNodes( | ||
| snapshot: Readonly<{ createdAt: number; nodes: SnapshotNode[] }>, | ||
| ): readonly Readonly<{ node: SnapshotNode; basis: ReplayDivergenceSuggestionBasis }>[]; | ||
| }>; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
packages/replay-port/src/daemon-port/__tests__/session-replay-maestro-request.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.