From 4eb939e7052ef8fcb896510134bd3a4eedfc8c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sat, 10 Oct 2026 23:49:00 +0200 Subject: [PATCH 1/4] refactor(daemon): hand the command surface to the daemon as an injected port The daemon no longer imports createCommandSurfaceAgentDevice; the composition root (src/daemon.ts) supplies it through the required createCommandSurface field threaded from RequestRouterDeps down to every execution runtime. The port shape lives in @agent-device/contracts/daemon-command-surface so naming it costs daemon files no type edge into the command families. --- .codemod-tmp.mjs | 109 ++++++ packages/contracts/package.json | 4 + .../contracts/src/daemon-command-surface.ts | 357 ++++++++++++++++++ src/__tests__/daemon-entrypoint.test.ts | 6 + ...ssion-test-reporter-values-maestro.test.ts | 4 +- .../session-test-reporter-values.test.ts | 4 +- src/daemon.ts | 6 +- src/daemon/__tests__/back-runtime.test.ts | 2 + .../__tests__/capture-disclosure.test.ts | 10 + .../__tests__/command-surface-fixture.ts | 10 + src/daemon/__tests__/daemon-policy.test.ts | 2 + src/daemon/__tests__/focus-runtime.test.ts | 2 + src/daemon/__tests__/generic-settle.test.ts | 7 +- .../http-server-limrun-uploaded-apps.test.ts | 2 + .../__tests__/human-control-router-fixture.ts | 2 + src/daemon/__tests__/is-runtime.test.ts | 40 +- .../__tests__/orientation-runtime.test.ts | 2 + ...n-replay-maestro-remote-evalscript.test.ts | 4 +- .../session-test-fail-fast.test.ts | 4 +- .../replay-suite/session-test-runner.test.ts | 28 +- .../session-test-suite-infrastructure.test.ts | 7 +- .../replay-suite/session-test-suite.test.ts | 52 ++- .../request-dispatch-disclosure.test.ts | 2 + .../__tests__/request-handler-catalog.test.ts | 4 +- ...quest-handler-chain-provider-scope.test.ts | 7 +- .../__tests__/request-handler-chain.test.ts | 3 + .../request-router-android-modal.test.ts | 4 + .../request-router-android-perf.test.ts | 2 + ...est-router-android-snapshot-helper.test.ts | 2 + .../request-router-apps-admission.test.ts | 2 + .../request-router-artifacts-web.test.ts | 2 + .../__tests__/request-router-cost.test.ts | 2 + ...request-router-custom-action-flags.test.ts | 2 + .../request-router-developer-dir.test.ts | 2 + .../request-router-error-redaction.test.ts | 2 + .../__tests__/request-router-events.test.ts | 8 + .../request-router-fill-text-stdin.test.ts | 2 + .../request-router-idle-expired.test.ts | 2 + .../request-router-lock-policy.test.ts | 10 + .../request-router-macos-app-lease.test.ts | 3 + .../request-router-open-claim.test.ts | 2 + .../__tests__/request-router-open.test.ts | 3 + ...equest-router-provider-credentials.test.ts | 2 + .../request-router-record-flags.test.ts | 2 + ...request-router-record-runtime-lock.test.ts | 3 + .../request-router-recording-health.test.ts | 3 + .../request-router-repair-expired.test.ts | 2 + .../request-router-replay-env.test.ts | 2 + .../request-router-replay-scope.test.ts | 5 + .../request-router-replay-test-ime.test.ts | 2 + .../request-router-response-level.test.ts | 2 + .../request-router-screenshot.test.ts | 3 + .../request-router-session-address.test.ts | 2 + .../request-router-typed-error.test.ts | 2 + .../request-runtime-binding-router.test.ts | 2 + .../request-save-script-transports.test.ts | 2 + .../__tests__/runtime-binding-conformance.ts | 2 + .../__tests__/screenshot-runtime.test.ts | 37 +- .../selector-capture-binding.test.ts | 7 +- ...elector-runtime-ambiguity-issuance.test.ts | 19 +- src/daemon/__tests__/selector-runtime.test.ts | 4 +- .../snapshot-command-runtime.test.ts | 13 +- ...shot-custom-actions-platform-guard.test.ts | 2 + .../__tests__/snapshot-diff-runtime.test.ts | 10 +- .../__tests__/snapshot-quality-latch.test.ts | 4 + .../snapshot-runtime-binding.test.ts | 4 +- .../snapshot-runtime-disclosure.test.ts | 2 + .../sparse-fallback-screenshot.test.ts | 4 +- .../__tests__/system-button-runtime.test.ts | 2 + .../__tests__/tv-remote-runtime.test.ts | 2 + src/daemon/__tests__/viewport-runtime.test.ts | 2 + src/daemon/__tests__/wait-runtime.test.ts | 10 +- src/daemon/generic-runtime-execution.ts | 2 + src/daemon/generic-settle.ts | 3 + .../handlers/__tests__/react-native.test.ts | 15 + .../__tests__/session-appstate-input.test.ts | 31 +- .../__tests__/session-boot-shutdown.test.ts | 49 ++- .../__tests__/session-command-replay.test.ts | 22 +- .../__tests__/session-device-claims.test.ts | 31 +- .../session-install-source-ref-frame.test.ts | 4 +- .../__tests__/session-push-ref-frame.test.ts | 4 +- .../handlers/__tests__/session-push.test.ts | 19 +- .../__tests__/session-reinstall.test.ts | 22 +- .../__tests__/session-relaunch-close.test.ts | 2 + .../__tests__/session-relaunch-guards.test.ts | 25 +- .../__tests__/session-replay-command.test.ts | 2 + .../__tests__/session-runtime-command.test.ts | 28 +- .../session-selector-dispatch.test.ts | 19 +- .../session-state-boot-warmup.test.ts | 4 +- .../__tests__/session-trigger.test.ts | 4 +- .../handlers/__tests__/snapshot-alert.test.ts | 2 + .../snapshot-handler-freshness.test.ts | 25 +- .../__tests__/snapshot-handler-wait.test.ts | 4 + .../__tests__/snapshot-handler.test.ts | 40 +- .../__tests__/snapshot-scoped-refs.test.ts | 2 + .../snapshot-settings-handler.test.ts | 73 ++-- .../__tests__/wait-landmark-recording.test.ts | 2 + src/daemon/handlers/session-command-input.ts | 2 + src/daemon/handlers/session.ts | 2 + .../handlers/snapshot-runtime-route.test.ts | 9 +- src/daemon/handlers/snapshot.ts | 8 + .../__tests__/find-handler-fixture.ts | 2 + .../__tests__/find-production-route.test.ts | 2 + .../find-target-activation-disclosure.test.ts | 2 + .../interaction-capture-disclosure.test.ts | 4 + .../__tests__/interaction-common.test.ts | 3 + .../interaction-dispatch-disclosure.test.ts | 19 +- .../interaction-gesture-drag.test.ts | 4 +- .../__tests__/interaction-get.test.ts | 14 + .../interaction-ios-tap-outcome.test.ts | 5 + ...nteraction-settle-private-ax-route.test.ts | 2 + .../interaction-target-evidence.test.ts | 2 + ...nteraction-touch-android-freshness.test.ts | 5 + ...nteraction-touch-android-readiness.test.ts | 5 + ...ction-touch-direct-ios-eligibility.test.ts | 5 + .../interaction-touch-direct-ios.test.ts | 2 + .../__tests__/interaction-touch-fill.test.ts | 5 + .../__tests__/interaction-touch-fixtures.ts | 3 + .../interaction-touch-payload.test.ts | 9 + .../interaction-touch-press-admission.test.ts | 3 + .../__tests__/interaction-touch-press.test.ts | 6 + .../interaction-touch-response.test.ts | 10 + .../interaction-touch-runtime.test.ts | 6 + .../__tests__/interaction-touch.test.ts | 9 + ...interaction-type-android-readiness.test.ts | 2 + src/daemon/interaction/internal/find.ts | 1 + .../internal/interaction-runtime.ts | 4 +- src/daemon/interaction/internal/types.ts | 5 + src/daemon/request-generic-dispatch.ts | 4 + src/daemon/request-handler-chain.ts | 7 + src/daemon/request-router.ts | 11 +- src/daemon/screenshot-runtime.ts | 10 +- src/daemon/selector-runtime-backend.ts | 6 +- .../server/daemon-runtime-idle-reap.test.ts | 4 + ...mon-runtime-interactor-composition.test.ts | 4 + .../daemon-runtime-lifecycle-shutdown.test.ts | 7 + .../daemon-runtime-metadata-ownership.test.ts | 4 +- .../daemon-runtime-recording-reaper.test.ts | 2 + .../daemon-runtime-snapshot-shutdown.test.ts | 2 + .../daemon-runtime-xctest-device-set.test.ts | 2 + src/daemon/server/daemon-runtime.ts | 6 +- .../__tests__/application.test.ts | 5 +- ...on-capabilities-install-projection.test.ts | 4 +- .../__tests__/session-capabilities.test.ts | 28 +- ...session-close-cwd-scoped-resources.test.ts | 4 +- .../session-close-error-precedence.test.ts | 16 +- .../session-close-lifecycle-runtime.test.ts | 4 +- .../session-close-resource-cleanup.test.ts | 14 +- .../session-close-save-script.test.ts | 10 +- .../session-close-shutdown-platform.test.ts | 22 +- .../session-devices-batch-runtime.test.ts | 4 +- .../session-open-execution-runtime.test.ts | 28 +- .../__tests__/session-open-existing.test.ts | 31 +- .../__tests__/session-open-foreground.test.ts | 17 +- .../__tests__/session-open-runtime.test.ts | 22 +- .../session-open-url-prewarm.test.ts | 2 + .../session-teardown-resources.test.ts | 7 +- .../internal/session-open-foreground.ts | 3 + .../internal/session-open.ts | 2 + .../__tests__/application.test.ts | 4 +- .../internal/__tests__/session-logs.test.ts | 4 +- .../session-network-runtime-parity.test.ts | 10 +- .../__tests__/session-network.test.ts | 7 +- src/daemon/snapshot-command-runtime.ts | 6 +- src/daemon/snapshot-runtime-binding.ts | 3 + src/daemon/snapshot-runtime.ts | 1 + src/daemon/sparse-fallback-screenshot.ts | 4 + .../integration/provider-scenarios/harness.ts | 2 + 168 files changed, 1510 insertions(+), 305 deletions(-) create mode 100644 .codemod-tmp.mjs create mode 100644 packages/contracts/src/daemon-command-surface.ts create mode 100644 src/daemon/__tests__/command-surface-fixture.ts diff --git a/.codemod-tmp.mjs b/.codemod-tmp.mjs new file mode 100644 index 0000000000..6825070ffe --- /dev/null +++ b/.codemod-tmp.mjs @@ -0,0 +1,109 @@ +// Codemod: insert `createCommandSurface: testCreateCommandSurface,` into object literals +// reported by tsc as missing the required port, and add the fixture import. +import fs from 'node:fs'; +import path from 'node:path'; +import { createRequire } from 'node:module'; +const ts = createRequire(import.meta.url)( + new URL('./node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/typescript.js', `file://${process.cwd()}/`).pathname, +); + +const ROOT = process.cwd(); +const FIXTURE = 'src/daemon/__tests__/command-surface-fixture.ts'; +const PROP = 'createCommandSurface'; +const VALUE = 'testCreateCommandSurface'; + +const errLines = fs + .readFileSync(process.argv[2], 'utf8') + .split('\n') + .filter((l) => /error TS(2741|2345)/.test(l) || /error TS2322/.test(l)); + +const byFile = new Map(); +for (const line of errLines) { + const m = line.match(/^(.+?)\((\d+),(\d+)\): error TS(\d+)/); + if (!m) continue; + const [, file, row, col, code] = m; + if (!/(^|\/)(__tests__|test)\//.test(file) && !file.includes('.test.ts')) continue; + if (!byFile.has(file)) byFile.set(file, []); + byFile.get(file).push({ row: +row, col: +col, code: +code }); +} + +let insertions = 0; +const changedFiles = new Set(); +const notes = []; + +for (const [relFile, errs] of byFile) { + const abs = path.join(ROOT, relFile); + if (!fs.existsSync(abs)) { + notes.push(`MISSING ${relFile}`); + continue; + } + const text = fs.readFileSync(abs, 'utf8'); + const src = ts.createSourceFile(abs, text, ts.ScriptTarget.Latest, true); + const lineStarts = ts.computeLineStarts(text); + const literals = []; + (function walk(node) { + if (ts.isObjectLiteralExpression(node)) literals.push(node); + node.forEachChild(walk); + })(src); + + const starts = new Set(); + for (const e of errs) { + const pos = lineStarts[e.row - 1] + e.col - 1; + let cands = literals + .filter((lit) => lit.getStart(src) <= pos && lit.getEnd() >= pos) + .sort((a, b) => b.getStart(src) - a.getStart(src)); + if (cands.length === 0) { + const lineEnd = lineStarts[e.row] ?? text.length; + cands = literals + .filter((lit) => lit.getStart(src) >= pos && lit.getStart(src) < lineEnd) + .sort((a, b) => a.getStart(src) - b.getStart(src)); + } + if (cands.length === 0) { + notes.push(`NO-LITERAL ${relFile} ${e.row} ${e.col} TS${e.code}`); + continue; + } + const lit = cands[0]; + if (lit.properties.some((p) => p.name && p.name.getText(src) === PROP)) continue; + starts.add(lit.getStart(src)); + } + if (starts.size === 0) continue; + + let out = text; + const edits = []; + for (const start of starts) { + const lit = literals.find((l) => l.getStart(src) === start); + const first = lit.properties[0]; + let indent = ' '; + if (first) { + const ls = text.lastIndexOf('\n', first.getStart(src)) + 1; + const m = text.slice(ls, first.getStart(src)).match(/[ \t]*$/); + if (m && m[0]) indent = m[0]; + } + const insertion = `\n${indent}${PROP}: ${VALUE},`; + const at = first ? first.getStart(src) : lit.getStart(src) + 1; + edits.push({ at, insertion }); + insertions += 1; + } + edits.sort((a, b) => b.at - a.at); + for (const e of edits) out = out.slice(0, e.at) + e.insertion + out.slice(e.at); + + if (!new RegExp(`import\\s*\\{[^}]*\\b${VALUE}\\b[^}]*\\}`).test(out)) { + const rel = path + .relative(path.dirname(abs), path.join(ROOT, FIXTURE)) + .replace(/\\/g, '/'); + const spec = rel.startsWith('.') ? rel : './' + rel; + const imp = `import { ${VALUE} } from '${spec}';`; + const tmp = ts.createSourceFile(abs, out, ts.ScriptTarget.Latest, true); + let lastImportEnd = 0; + for (const st of tmp.statements) { + if (ts.isImportDeclaration(st)) lastImportEnd = st.getEnd(); + else break; + } + if (lastImportEnd > 0) out = out.slice(0, lastImportEnd) + '\n' + imp + out.slice(lastImportEnd); + else out = imp + '\n' + out; + } + fs.writeFileSync(abs, out); + changedFiles.add(relFile); +} +console.log('FILES', changedFiles.size, 'INSERTIONS', insertions); +for (const n of notes) console.log(n); diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 17f3424e02..b4f956b462 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -168,6 +168,10 @@ "types": "./src/command-policy.ts", "default": "./src/command-policy.ts" }, + "./daemon-command-surface": { + "types": "./src/daemon-command-surface.ts", + "default": "./src/daemon-command-surface.ts" + }, "./daemon-http": { "types": "./src/daemon-http.ts", "default": "./src/daemon-http.ts" diff --git a/packages/contracts/src/daemon-command-surface.ts b/packages/contracts/src/daemon-command-surface.ts new file mode 100644 index 0000000000..8f25f2b8f0 --- /dev/null +++ b/packages/contracts/src/daemon-command-surface.ts @@ -0,0 +1,357 @@ +// The command surface a host injects into the daemon, stated as the daemon needs it: the +// daemon's reach into the command surface without a type edge into `src/commands/`. The +// composition root assigns `createCommandSurfaceAgentDevice` to this type, so the compiler +// checks every signature against the implementation on each build. + +import type { SessionSurface } from './session-surface.ts'; +import type { ArtifactDescriptor, FileOutputRef } from './artifact-adapter.ts'; +import type { DeviceRotation } from './device-rotation.ts'; +import type { ClickButton } from './click-button.ts'; +import type { DiffSnapshotCommandResult } from './diff.ts'; +import type { SnapshotDiagnosticsSummary } from './snapshot-diagnostics.ts'; +import type { PublicSnapshotCaptureAnnotations } from './snapshot-capture-annotations.ts'; +import type { ReplayTargetGuardDenotation } from './replay.ts'; +import type { TargetAnnotationV1 } from './target-annotation.ts'; +import type { + DragGestureInput, + GestureIntent, + GestureSemanticInput, +} from './gesture-plan-types.ts'; +import type { FindLocator } from './snapshot-types.ts'; +import type { + FillCommandResult, + FindReadResult, + HoverCommandResult, + InteractionTarget, + LongPressCommandResult, + PressCommandResult, + PreresolvedInteractionTarget, + RepeatedInput, + ResolutionDisclosure, + ResolvedTarget, + SettleObservation, + SettleParams, + SurfaceScopedNodes, +} from './interaction.ts'; +import type { IsPredicate } from './is-predicate.ts'; +import type { AgentDeviceRuntimeConfig, CommandContext } from './runtime-contract.ts'; +import type { + Point, + SnapshotCommandOptionFields, + SnapshotKeyboardBandFact, + SnapshotNode, + SnapshotUnchanged, + SnapshotViewportSize, +} from '@agent-device/kernel/snapshot'; + +/** The `--verify` / `--settle` post-action observation the touch routes forward. */ +export type DaemonPostActionOptions = Readonly<{ + verify?: boolean; + settle?: SettleParams; +}>; + +/** The snapshot-scoping options a selector read carries alongside its target. */ +export type DaemonSelectorSnapshotOptions = Readonly<{ + depth?: number; + scope?: string; + raw?: boolean; +}>; + +/** The options of the daemon's `snapshot` / `diff snapshot` calls. */ +export type DaemonSnapshotOptions = CommandContext & SnapshotCommandOptionFields; + +/** What the daemon's `screenshot` route and its two observation paths receive. */ +export type DaemonScreenshotResult = Readonly<{ + path: string; + artifacts?: ArtifactDescriptor[]; + displayRotation?: DeviceRotation; + message?: string; + warnings?: string[]; +}>; + +/** What the daemon's `snapshot` route publishes and its sparse-quality guard reads. */ +export type DaemonSnapshotResult = Readonly< + { + nodes: SnapshotNode[]; + truncated?: boolean; + appName?: string; + appBundleId?: string; + unchanged?: SnapshotUnchanged; + snapshotDiagnostics?: SnapshotDiagnosticsSummary; + keyboard?: SnapshotKeyboardBandFact; + viewport?: SnapshotViewportSize; + } & PublicSnapshotCaptureAnnotations +>; + +export type DaemonScreenshotCommandOptions = CommandContext & + Readonly<{ + out?: FileOutputRef; + fullscreen?: boolean; + overlayRefs?: boolean; + pixelDensity?: number; + scale?: number; + stabilize?: boolean; + normalizeStatusBar?: boolean; + appId?: string; + appBundleId?: string; + surface?: SessionSurface; + }>; + +export type DaemonFindOptions = CommandContext & + DaemonSelectorSnapshotOptions & + Readonly<{ + locator?: FindLocator; + query: string; + action: 'exists' | 'wait' | 'get_text' | 'get_attrs' | 'list'; + timeoutMs?: number; + }>; + +export type DaemonGetOptions = CommandContext & + DaemonSelectorSnapshotOptions & + Readonly<{ + property: 'text' | 'attrs'; + target: InteractionTarget; + /** ADR 0012 step 4: replay-only post-resolution guard. */ + expectedResolvedTarget?: ReplayTargetGuardDenotation; + }>; + +/** + * A resolved read reports the element, its value, and the tree it resolved against + * (#1349 record-time evidence). + */ +export type DaemonGetResult = + | Readonly<{ + kind: 'text'; + target: ResolvedTarget; + text: string; + node: SnapshotNode; + selectorChain?: string[]; + preActionNodes: SnapshotNode[]; + }> + | Readonly<{ + kind: 'attrs'; + target: ResolvedTarget; + node: SnapshotNode; + selectorChain?: string[]; + preActionNodes: SnapshotNode[]; + }>; + +export type DaemonIsOptions = CommandContext & + DaemonSelectorSnapshotOptions & + Readonly<{ + predicate: IsPredicate; + selector: string; + expectedText?: string; + expectedResolvedTarget?: ReplayTargetGuardDenotation; + }>; + +export type DaemonIsResult = Readonly<{ + predicate: IsPredicate; + pass: true; + selector: string; + matches?: number; + text?: string; + selectorChain?: string[]; + node?: SnapshotNode; + preActionNodes?: SnapshotNode[]; +}>; + +export type DaemonWaitTarget = + | { kind: 'sleep'; durationMs: number } + | { kind: 'text'; text: string; timeoutMs?: number | null } + | { kind: 'ref'; ref: string; timeoutMs?: number | null } + | { + kind: 'selector'; + selector: string; + timeoutMs?: number | null; + /** ADR 0012 / #1349: the recorded landmark identity a replayed wait must observe. */ + recordedLandmark?: TargetAnnotationV1; + } + | { kind: 'absent'; selector: string; timeoutMs?: number | null } + | { kind: 'stable'; quietMs?: number | null; timeoutMs?: number | null }; + +export type DaemonWaitOptions = CommandContext & + DaemonSelectorSnapshotOptions & + Readonly<{ target: DaemonWaitTarget }>; + +export type DaemonWaitResult = Readonly<{ + kind: 'sleep' | 'text' | 'selector' | 'absent' | 'stable'; + waitedMs: number; + text?: string; + selector?: string; + captures?: number; + nodeCount?: number; + hint?: string; + node?: SnapshotNode; + preActionNodes?: SnapshotNode[]; +}>; + +export type DaemonWaitForTextOptions = CommandContext & + DaemonSelectorSnapshotOptions & + Readonly<{ text: string; timeoutMs?: number | null }>; + +export type DaemonPressOptions = CommandContext & + RepeatedInput & + DaemonPostActionOptions & + Readonly<{ + target: InteractionTarget; + button?: ClickButton; + /** Polls for the target to become actionable; absent takes one attempt. */ + readinessTimeoutMs?: number; + expectedResolvedTarget?: ReplayTargetGuardDenotation; + /** #1654: a mutating `find`'s already-resolved node. */ + preresolvedTarget?: PreresolvedInteractionTarget; + }>; + +export type DaemonFillOptions = CommandContext & + DaemonPostActionOptions & + Readonly<{ + target: InteractionTarget; + text: string; + delayMs?: number; + /** Internal Maestro replay policy, forwarded only to the platform action. */ + allowNonHittableCoordinateFallback?: boolean; + expectedResolvedTarget?: ReplayTargetGuardDenotation; + preresolvedTarget?: PreresolvedInteractionTarget; + }>; + +export type DaemonLongPressOptions = CommandContext & + Readonly<{ + target: InteractionTarget; + durationMs?: number; + readinessTimeoutMs?: number; + expectedResolvedTarget?: ReplayTargetGuardDenotation; + settle?: SettleParams; + }>; + +export type DaemonHoverOptions = CommandContext & + Readonly<{ + target: InteractionTarget; + expectedResolvedTarget?: ReplayTargetGuardDenotation; + settle?: SettleParams; + }>; + +export type DaemonGestureOptions = CommandContext & + ( + | Readonly<{ gesture: GestureSemanticInput }> + | Readonly<{ + gesture: DragGestureInput; + expectedResolvedTargets?: { + source?: ReplayTargetGuardDenotation; + destination?: ReplayTargetGuardDenotation; + }; + }> + ); + +/** A coordinate gesture reports the frame it settled on. */ +export type DaemonCoordinateGestureResult = Readonly<{ + kind: GestureIntent; + durationMs: number; + pointerCount: 1 | 2; + from: Point; + to: Point; + backendResult?: Record; + message?: string; +}>; + +/** A drag additionally reports its two endpoints and what a replay needs to re-issue them. */ +export type DaemonDragGestureResult = Readonly<{ + kind: 'drag'; + durationMs: number; + pointerCount: 1; + from: Point; + to: Point; + backendResult?: Record; + message?: string; + targets: Readonly<{ + source: DaemonDragEndpointDisclosure; + destination: DaemonDragEndpointDisclosure; + }>; + recording?: Readonly<{ + sourceSelector?: string; + destinationSelector?: string; + sourceTarget?: DaemonDragRecordingTarget; + destinationTarget?: DaemonDragRecordingTarget; + }>; +}>; + +export type DaemonGestureResult = DaemonCoordinateGestureResult | DaemonDragGestureResult; + +type DaemonDragEndpointDisclosure = Readonly<{ + selectorChain?: string[]; + resolution: ResolutionDisclosure; +}>; + +type DaemonDragRecordingTarget = Readonly<{ + selectorChain: string[]; + node: SnapshotNode; + preActionNodes: SnapshotNode[]; +}>; + +export type DaemonSettleObservationOptions = CommandContext & + SettleParams & + Readonly<{ + /** The pre-action tree the settled diff is taken against, and the surface it describes. */ + baseline: SurfaceScopedNodes; + }>; + +/** The three captures the daemon runs. */ +export type DaemonCommandSurfaceCapture = Readonly<{ + screenshot(options: DaemonScreenshotCommandOptions): Promise; + snapshot(options: DaemonSnapshotOptions): Promise; + diffSnapshot(options: DaemonSnapshotOptions): Promise; +}>; + +/** The five selector reads the daemon's selector and wait routes serve. */ +export type DaemonCommandSurfaceSelectors = Readonly<{ + find(options: DaemonFindOptions): Promise; + get(options: DaemonGetOptions): Promise; + is(options: DaemonIsOptions): Promise; + wait(options: DaemonWaitOptions): Promise; + waitForText( + text: string, + options?: Omit, + ): Promise; +}>; + +/** The seven actions the daemon's touch, gesture and settle routes serve. */ +export type DaemonCommandSurfaceInteractions = Readonly<{ + click( + target: InteractionTarget, + options?: Omit, + ): Promise; + press( + target: InteractionTarget, + options?: Omit, + ): Promise; + fill( + target: InteractionTarget, + text: string, + options?: Omit, + ): Promise; + longPress( + target: InteractionTarget, + options?: Omit, + ): Promise; + hover( + target: InteractionTarget, + options?: Omit, + ): Promise; + gesture(options: DaemonGestureOptions): Promise; + settleObservation(options: DaemonSettleObservationOptions): Promise; +}>; + +/** What a daemon route may call, and nothing else. */ +export type DaemonCommandSurface = Readonly<{ + /** The cancellation signal this surface's runtime was assembled with. */ + signal?: AbortSignal; + capture: DaemonCommandSurfaceCapture; + selectors: DaemonCommandSurfaceSelectors; + interactions: DaemonCommandSurfaceInteractions; +}>; + +/** + * The injection seam: one command surface per request-bound runtime config. The composition root + * supplies `createCommandSurfaceAgentDevice`; a test supplies a fake through the same seam. + */ +export type CreateDaemonCommandSurface = (config: AgentDeviceRuntimeConfig) => DaemonCommandSurface; diff --git a/src/__tests__/daemon-entrypoint.test.ts b/src/__tests__/daemon-entrypoint.test.ts index 74a49056a8..fe078fcdb6 100644 --- a/src/__tests__/daemon-entrypoint.test.ts +++ b/src/__tests__/daemon-entrypoint.test.ts @@ -24,6 +24,7 @@ import { screenRecordingDurableResource } from '@agent-device/capture-kit/screen import { closeLoopbackServer, listenOnLoopback, waitForHttpOk } from './test-utils/loopback.ts'; import { mkdtempForTestSync } from './test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../daemon/__tests__/command-surface-fixture.ts'; type DaemonInfoFile = { httpPort?: number; @@ -87,6 +88,7 @@ test('daemon runtime starts HTTP transport in-process and shuts down cleanly', a try { const runtime = await startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -146,6 +148,7 @@ test('daemon runtime publishes dual transport metadata', async () => { try { const runtime = await startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -196,6 +199,7 @@ test('daemon rejects unowned cloud artifacts over RPC', async () => { try { runtime = await startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -326,6 +330,7 @@ test('daemon runtime records startup device-claim reconciliation in daemon.log', ); const runtime = await startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -451,6 +456,7 @@ test('startup sweep settles a foreign dead owner without touching a live same-na ); runtime = await startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: daemonStateDir, diff --git a/src/cli/replay-test/__tests__/session-test-reporter-values-maestro.test.ts b/src/cli/replay-test/__tests__/session-test-reporter-values-maestro.test.ts index e1fc8ec8ca..e9ba4d57cc 100644 --- a/src/cli/replay-test/__tests__/session-test-reporter-values-maestro.test.ts +++ b/src/cli/replay-test/__tests__/session-test-reporter-values-maestro.test.ts @@ -20,6 +20,7 @@ import { daemonPlatformServicesFixture } from '../../../daemon/__tests__/platfor // Maestro replay resolves a target device through core/dispatch. Gesture viewport reads re-enter // the admitted internal runtime command; the invoke fixture below returns its typed viewport. import { expect, test, vi } from 'vitest'; +import { testCreateCommandSurface } from '../../../daemon/__tests__/command-surface-fixture.ts'; vi.mock('@agent-device/device-selection/dispatch-resolve', async (importOriginal) => { const actual = @@ -113,7 +114,8 @@ async function runMaestroSuiteThroughReporter(params: { (event) => runReplayTestReporterProgress(reporters, event, reporterContext), async () => await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/cli/replay-test/__tests__/session-test-reporter-values.test.ts b/src/cli/replay-test/__tests__/session-test-reporter-values.test.ts index 5b64aaea2b..387d2ef11a 100644 --- a/src/cli/replay-test/__tests__/session-test-reporter-values.test.ts +++ b/src/cli/replay-test/__tests__/session-test-reporter-values.test.ts @@ -12,6 +12,7 @@ import { daemonPlatformServicesFixture } from '../../../daemon/__tests__/platfor // ADR 0012 attempts a post-failure screen digest through the narrow snapshot interactor seam; // these fixtures model no runner, so reject that leaf capture deterministically. import { expect, test, vi } from 'vitest'; +import { testCreateCommandSurface } from '../../../daemon/__tests__/command-surface-fixture.ts'; vi.mock('../../../daemon/snapshot-interactor-capture.ts', () => ({ captureSnapshotWithInteractor: vi.fn(async () => { @@ -103,7 +104,8 @@ async function runSuiteThroughReporter(params: { (event) => runReplayTestReporterProgress(reporters, event, reporterContext), async () => await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon.ts b/src/daemon.ts index facd1dbf08..6a432496ab 100644 --- a/src/daemon.ts +++ b/src/daemon.ts @@ -1,7 +1,11 @@ +import { createCommandSurfaceAgentDevice } from './command-runtime/runtime-command-surface.ts'; import { startDaemonRuntime } from './daemon/server/daemon-runtime.ts'; import { asAppError } from '@agent-device/kernel/errors'; -void startDaemonRuntime().catch((error) => { +void startDaemonRuntime({ + // This executable is the composition root that builds the daemon's command surface. + createCommandSurface: createCommandSurfaceAgentDevice, +}).catch((error) => { const appErr = asAppError(error); process.stderr.write(`Daemon error: ${appErr.message}\n`); process.exit(1); diff --git a/src/daemon/__tests__/back-runtime.test.ts b/src/daemon/__tests__/back-runtime.test.ts index 9e3e518801..2f34840773 100644 --- a/src/daemon/__tests__/back-runtime.test.ts +++ b/src/daemon/__tests__/back-runtime.test.ts @@ -24,6 +24,7 @@ import type { GenericPlatformExecutionParams } from '../generic-leaf-execution.t import { resolveBoundBackRuntime } from '../back-runtime.ts'; import { expectRefusesUnavailableExactOwnerFact } from './runtime-binding-conformance.ts'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; // File-scoped id, not the widely shared 'ios-simulator' literal: this owner binding's // `local-family` kind reaches the real on-disk device-claim admission (`require-owner` @@ -143,6 +144,7 @@ test('request router joins back admission to execution, recording, and ref inval activateCompleteRefFrame(session); sessionStore.publish(session.name, session); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: '/tmp/daemon.log', token: 't', sessionStore, diff --git a/src/daemon/__tests__/capture-disclosure.test.ts b/src/daemon/__tests__/capture-disclosure.test.ts index 59459f360f..248d643458 100644 --- a/src/daemon/__tests__/capture-disclosure.test.ts +++ b/src/daemon/__tests__/capture-disclosure.test.ts @@ -14,6 +14,7 @@ import { makeIosSession, } from '../../__tests__/test-utils/session-factories.ts'; import { platformResourceCleanup } from '../../platform-runtime-resource-cleanup.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; vi.mock('@agent-device/device-selection/dispatch-resolve', async (importOriginal) => { const actual = @@ -78,6 +79,7 @@ test('mutating find on a system-surface capture discloses the occlusion on the f sessionStore.publish('default', session); const response = await handleFindCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: 'default', @@ -105,6 +107,7 @@ test('read-only find exists on a system-surface capture discloses the occlusion' sessionStore.publish('default', session); const response = await dispatchFindReadOnlyViaRuntime({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: 'default', @@ -131,6 +134,7 @@ test('wait timeout for app text hidden behind a system surface discloses the occ sessionStore.publish('default', session); const response = await dispatchWaitViaRuntime({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: 'default', @@ -156,6 +160,7 @@ test('sessionless read-only find still discloses the occluding system surface', const sessionStore = makeSessionStore(); const response = await dispatchFindReadOnlyViaRuntime({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: 'default', @@ -217,6 +222,7 @@ test('sessionless wait success on shade content still discloses the occluding sy const sessionStore = makeSessionStore(); const response = await dispatchWaitViaRuntime({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: 'default', @@ -244,6 +250,7 @@ test('sessionless wait timeout still discloses the occluding system surface', as const sessionStore = makeSessionStore(); const response = await dispatchWaitViaRuntime({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: 'default', @@ -307,6 +314,7 @@ test('mutating find on an in-place system surface discloses it on the found outc sessionStore.publish('default', makeIosSession('default', { appBundleId: 'com.example.app' })); const response = await handleFindCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: 'default', @@ -334,6 +342,7 @@ test('mutating find that misses on an in-place system surface still discloses it sessionStore.publish('default', makeIosSession('default', { appBundleId: 'com.example.app' })); const response = await handleFindCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: 'default', @@ -396,6 +405,7 @@ test('wait timeout whose polls required a foreground repair discloses the repair }); const response = await dispatchWaitViaRuntime({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: 'default', diff --git a/src/daemon/__tests__/command-surface-fixture.ts b/src/daemon/__tests__/command-surface-fixture.ts new file mode 100644 index 0000000000..050b0a7640 --- /dev/null +++ b/src/daemon/__tests__/command-surface-fixture.ts @@ -0,0 +1,10 @@ +import { createCommandSurfaceAgentDevice } from '../../command-runtime/runtime-command-surface.ts'; + +/** + * The required `createCommandSurface` port for tests that build daemon param bags directly. + * Tests reach the device through vi.mock'd seams, so a real factory behind the port never runs + * command code in these suites; naming the port costs each call site one line, not a fake. + * The concrete function type (not the narrowed port alias) keeps runtime members like `signal` + * visible to the few tests that read them off an assembled runtime. + */ +export const testCreateCommandSurface = createCommandSurfaceAgentDevice; diff --git a/src/daemon/__tests__/daemon-policy.test.ts b/src/daemon/__tests__/daemon-policy.test.ts index 76ca96a706..66979f1c9c 100644 --- a/src/daemon/__tests__/daemon-policy.test.ts +++ b/src/daemon/__tests__/daemon-policy.test.ts @@ -2,6 +2,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { beforeEach, expect, test, vi } from 'vitest'; import { getResolveTargetDeviceMock } from './request-router-dispatch-mocks.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; vi.mock('../device/device-ready.ts', () => ({ ensureDeviceReady: vi.fn(async () => {}) })); @@ -48,6 +49,7 @@ function makeHandler( const bind = vi.fn(lifecycleDeviceRuntimeGateway.bind); const inspectFacts = vi.fn(lifecycleDeviceRuntimeGateway.inspectFacts); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon-policy'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/focus-runtime.test.ts b/src/daemon/__tests__/focus-runtime.test.ts index 54af0db735..57b03baa86 100644 --- a/src/daemon/__tests__/focus-runtime.test.ts +++ b/src/daemon/__tests__/focus-runtime.test.ts @@ -24,6 +24,7 @@ import type { GenericPlatformExecutionParams } from '../generic-leaf-execution.t import { readFocusPoint, resolveBoundFocusRuntime } from '../focus-runtime.ts'; import { expectRefusesUnavailableExactOwnerFact } from './runtime-binding-conformance.ts'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; const appleDevice = { id: 'ios-simulator', @@ -195,6 +196,7 @@ test('request router joins focus admission to execution, recording, and ref inva activateCompleteRefFrame(session); sessionStore.publish(session.name, session); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: '/tmp/daemon.log', token: 't', sessionStore, diff --git a/src/daemon/__tests__/generic-settle.test.ts b/src/daemon/__tests__/generic-settle.test.ts index c37af36f13..2247681a1c 100644 --- a/src/daemon/__tests__/generic-settle.test.ts +++ b/src/daemon/__tests__/generic-settle.test.ts @@ -16,6 +16,7 @@ import type { SessionStore } from '../session-store.ts'; import type { DaemonRequest, DaemonResponse } from '../daemon-request.ts'; import type { SessionRef, SessionState } from '../session-state.ts'; import { buildSnapshotState } from '@agent-device/capture-kit/snapshot-state'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; // #1638 `--settle` on the GENERIC daemon route (scroll/back): the settled diff, // its refs, and the ref-frame/generation dance are the same contract the touch @@ -187,7 +188,8 @@ async function dispatchGeneric(params: { ...(params.flags ? { flags: params.flags } : {}), }; return await dispatchGenericCommand({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req, ref: params.sessionStore.lookup(params.sessionName)!, sessionName: params.sessionName, @@ -560,7 +562,8 @@ test('an ended generic lifetime is refused before dispatch or settle constructio mockCommandDispatch([AFTER_NODES]); await expect( dispatchGenericCommand({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, diff --git a/src/daemon/__tests__/http-server-limrun-uploaded-apps.test.ts b/src/daemon/__tests__/http-server-limrun-uploaded-apps.test.ts index 9d9effa909..1744d20412 100644 --- a/src/daemon/__tests__/http-server-limrun-uploaded-apps.test.ts +++ b/src/daemon/__tests__/http-server-limrun-uploaded-apps.test.ts @@ -20,6 +20,7 @@ import { LeaseRegistry } from '../lease-registry.ts'; import { createDaemonHttpServer } from '../server/http-server.ts'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; const limrunIo = vi.hoisted(() => ({ listAssets: vi.fn(), @@ -52,6 +53,7 @@ test('public HTTP rejects Limrun uploaded-app listing and allocation before prov const token = 'limrun-http-test-token'; const leaseRegistry = new LeaseRegistry(); const handleRequest = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join( mkdtempForTestSync('agent-device-limrun-http-access'), 'agent-device-limrun-http-access.log', diff --git a/src/daemon/__tests__/human-control-router-fixture.ts b/src/daemon/__tests__/human-control-router-fixture.ts index ce31f103fe..a29a4a6f59 100644 --- a/src/daemon/__tests__/human-control-router-fixture.ts +++ b/src/daemon/__tests__/human-control-router-fixture.ts @@ -12,6 +12,7 @@ import { readDaemonProviderCredentials } from '../../provider-credential-fingerp import { tenantScopedSessionName } from '../session-tenant-scope.ts'; import { lifecycleDeviceRuntimeGateway } from './test-device-runtime-gateway.ts'; import { HUMAN_CONTROL_LEASE_REQUEST, humanControlRequest } from './human-control-fixtures.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; export function createHumanControlHarness() { const registry = new LeaseRegistry(); @@ -25,6 +26,7 @@ export function createHumanControlHarness() { }), ); const handleRequest = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: '/tmp/agent-device-human-control.log', platformServices: daemonPlatformServicesFixture(), claimRecoveryGateway: daemonClaimRecoveryGatewayFixture(), diff --git a/src/daemon/__tests__/is-runtime.test.ts b/src/daemon/__tests__/is-runtime.test.ts index 1facdf6acf..ad66b85530 100644 --- a/src/daemon/__tests__/is-runtime.test.ts +++ b/src/daemon/__tests__/is-runtime.test.ts @@ -16,6 +16,7 @@ import type { DaemonRequest } from '../daemon-request.ts'; import { selectorCaptureFixture } from './selector-capture-fixture.ts'; import { markDeferredInteractionOutcome } from '../deferred-interaction-outcome.ts'; import { formatPostGestureOutcomeWarning } from '@agent-device/capture-kit/post-gesture-stability'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; const { mockRunAppleRunnerCommand } = vi.hoisted(() => ({ mockRunAppleRunnerCommand: vi.fn() })); @@ -81,7 +82,8 @@ test('an admitted is inspects once, binds once, and answers through the bound ca ); const response = await dispatchIsViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: isRequest('is-bound', ['visible', 'id=auth_continue']), sessionName: 'is-bound', sessionStore, @@ -103,7 +105,8 @@ test('is absent uses the bound readAny capture for selector-first input without sessionStore.publish('is-absent', makeIosAppSession('is-absent')); const response = await dispatchIsViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: isRequest('is-absent', ['label="Removed row"', 'absent']), sessionName: 'is-absent', sessionStore, @@ -145,7 +148,8 @@ test('is absent bypasses a cached no-match snapshot before evaluating the bound ); const response = await dispatchIsViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: isRequest('is-absent-fresh', ['absent', 'label="Removed row"']), sessionName: 'is-absent-fresh', sessionStore, @@ -178,7 +182,8 @@ test('is absent fails closed for a quality-less legacy iOS root-only capture', a sessionStore.publish('is-legacy-sparse', makeIosAppSession('is-legacy-sparse')); const response = await dispatchIsViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: isRequest('is-legacy-sparse', ['absent', 'label="Removed row"']), sessionName: 'is-legacy-sparse', sessionStore, @@ -215,7 +220,8 @@ test('is absent rejects depth and scope before binding with typed invalid argume ['snapshotDepth', 2], ] as const) { const response = await dispatchIsViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: isRequest('is-absent-flags', ['absent', 'label="Gone"'], { [flag]: value }), sessionName: 'is-absent-flags', sessionStore, @@ -245,7 +251,8 @@ test('an unavailable capture fact refuses before any bind', async () => { sessionStore.publish('is-refused', makeAndroidSession('is-refused', { appBundleId: 'com.a' })); const response = await dispatchIsViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: isRequest('is-refused', ['visible', 'id=auth_continue']), sessionName: 'is-refused', sessionStore, @@ -278,7 +285,8 @@ test('an iOS session with no tracked app is refused with the open hint, not answ {}, async () => await dispatchIsViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: isRequest('is-no-app', ['visible', 'id=auth_continue']), sessionName: 'is-no-app', sessionStore, @@ -305,7 +313,8 @@ test('an iOS session WITH a tracked app still answers, so the refusal is the pla sessionStore.publish('is-with-app', makeIosAppSession('is-with-app')); const response = await dispatchIsViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: isRequest('is-with-app', ['visible', 'label=Continue']), sessionName: 'is-with-app', sessionStore, @@ -323,7 +332,8 @@ test('an Android session with no tracked app proceeds, because the owner adverti sessionStore.publish('is-android-no-app', makeAndroidSession('is-android-no-app')); const response = await dispatchIsViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: isRequest('is-android-no-app', ['visible', 'id=auth_continue']), sessionName: 'is-android-no-app', sessionStore, @@ -360,7 +370,8 @@ test('a refused request reaches the device by no route at all', async () => { }); const response = await dispatchIsViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: isRequest('is-direct-refused', ['selected', 'id="shipping-pickup"']), sessionName: 'is-direct-refused', sessionStore, @@ -421,7 +432,8 @@ test('a failing predicate answers COMMAND_FAILED from the bound capture', async }); const response = await dispatchIsViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: isRequest('is-direct-false', ['text', 'id=account_row', 'Wrong Expected Text']), sessionName: 'is-direct-false', sessionStore, @@ -462,7 +474,8 @@ test('a miss on a surface that never settled carries the unsettled fact, and the sessionStore.publish('is-unsettled', session); const isVisible = () => dispatchIsViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: isRequest('is-unsettled', ['visible', 'id=target']), sessionName: 'is-unsettled', sessionStore, @@ -505,7 +518,8 @@ test('a read after a scroll that moved nothing carries the no-effect outcome, an sessionStore.publish('is-no-effect', session); const isVisible = () => dispatchIsViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: isRequest('is-no-effect', ['visible', 'id=row']), sessionName: 'is-no-effect', sessionStore, diff --git a/src/daemon/__tests__/orientation-runtime.test.ts b/src/daemon/__tests__/orientation-runtime.test.ts index 0a86a7186d..7bf4a00723 100644 --- a/src/daemon/__tests__/orientation-runtime.test.ts +++ b/src/daemon/__tests__/orientation-runtime.test.ts @@ -1,5 +1,6 @@ import { daemonPlatformServicesFixture } from './platform-services-fixture.ts'; import { expect, test, vi } from 'vitest'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; // `orientation` carries `androidBlockingDialogGuard: true` (like every other generic-route leaf // in this migration), so this file's Android device reaching the real request router below hits @@ -201,6 +202,7 @@ test('request router joins orientation admission to execution and ref invalidati activateCompleteRefFrame(session); sessionStore.publish(session.name, session); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: '/tmp/daemon.log', token: 't', sessionStore, diff --git a/src/daemon/__tests__/replay-maestro/session-replay-maestro-remote-evalscript.test.ts b/src/daemon/__tests__/replay-maestro/session-replay-maestro-remote-evalscript.test.ts index 5c3240141b..0c20556aa6 100644 --- a/src/daemon/__tests__/replay-maestro/session-replay-maestro-remote-evalscript.test.ts +++ b/src/daemon/__tests__/replay-maestro/session-replay-maestro-remote-evalscript.test.ts @@ -20,6 +20,7 @@ import { } from '../../handlers/session-replay-command.ts'; import type { SessionCommandParams } from '../../handlers/session-command-input.ts'; import * as maestro from '@agent-device/maestro'; +import { testCreateCommandSurface } from '../command-surface-fixture.ts'; const spy = vi.spyOn(maestro, 'executeMaestroFlow'); @@ -72,7 +73,8 @@ async function runWithNetworkFlag(publicNetworkOnly: boolean | undefined) { // the asserted path: dropping it in the handler must fail this test, since an // unset field reads as trusted and a remote flow would run evalScript. const response = await handleReplayCommand({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req, sessionName: 'default', logPath: path.join(root, 'daemon.log'), diff --git a/src/daemon/__tests__/replay-suite/session-test-fail-fast.test.ts b/src/daemon/__tests__/replay-suite/session-test-fail-fast.test.ts index a8e3f08cb0..7b7df2912c 100644 --- a/src/daemon/__tests__/replay-suite/session-test-fail-fast.test.ts +++ b/src/daemon/__tests__/replay-suite/session-test-fail-fast.test.ts @@ -7,6 +7,7 @@ import { makeSessionStore } from '../../../__tests__/test-utils/store-factory.ts import { handleSessionCommands } from '../../handlers/__tests__/session-command-harness.ts'; import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../command-surface-fixture.ts'; test('test --fail-fast continues after passing scripts', async () => { const root = mkdtempForTestSync('agent-device-test-fail-fast-pass-'); @@ -15,7 +16,8 @@ test('test --fail-fast continues after passing scripts', async () => { const invokedPaths: string[] = []; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/__tests__/replay-suite/session-test-runner.test.ts b/src/daemon/__tests__/replay-suite/session-test-runner.test.ts index 7d2cd08116..4ce91f039d 100644 --- a/src/daemon/__tests__/replay-suite/session-test-runner.test.ts +++ b/src/daemon/__tests__/replay-suite/session-test-runner.test.ts @@ -14,6 +14,7 @@ import type { DaemonRequest } from '../../daemon-request.ts'; import { handleSessionCommands } from '../../handlers/__tests__/session-command-harness.ts'; import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../command-surface-fixture.ts'; // Opening a Simulator schedules a best-effort runner prewarm that outlives the request; a real one // would spawn xcodebuild after this file finished and land in whichever file the worker runs next. @@ -67,7 +68,8 @@ test('session_list includes device_udid and ios_simulator_device_set for iOS ses ); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', command: 'session_list', positionals: [] }, sessionName: 'default', logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), @@ -103,7 +105,8 @@ test('test filters replay scripts by context platform and skips untyped files', const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -174,7 +177,8 @@ test('test binds each replay script to its declared platform metadata', async () let response; try { response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -213,7 +217,8 @@ test('test cleans up suite-owned sessions after each executed script', async () fs.writeFileSync(path.join(root, '01-android.ad'), 'context platform=android\nopen "Demo"\n'); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -253,7 +258,8 @@ test('test retries failed scripts with fresh suite-owned sessions', async () => const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -307,7 +313,8 @@ test('test applies per-script timeout and writes attempt artifacts', async () => let invocationCount = 0; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -375,7 +382,8 @@ test('open does not retain a session when the request was canceled before comple markRequestCanceled(requestId); try { const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -407,7 +415,8 @@ test('test returns invalid args when no replay scripts match the platform filter fs.writeFileSync(path.join(root, '01-ios.ad'), 'context platform=ios\nopen "Settings"\n'); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -437,7 +446,8 @@ test('test rejects duplicate replay test metadata in the context header', async ); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/__tests__/replay-suite/session-test-suite-infrastructure.test.ts b/src/daemon/__tests__/replay-suite/session-test-suite-infrastructure.test.ts index 329701e398..868b41c67d 100644 --- a/src/daemon/__tests__/replay-suite/session-test-suite-infrastructure.test.ts +++ b/src/daemon/__tests__/replay-suite/session-test-suite-infrastructure.test.ts @@ -8,6 +8,7 @@ import type { DaemonResponse } from '../../daemon-request.ts'; import { handleSessionCommands } from '../../handlers/__tests__/session-command-harness.ts'; import { expectOkData, makeSessionStore } from './session-test-suite.fixtures.ts'; import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../command-surface-fixture.ts'; vi.mock('../../snapshot-interactor-capture.ts', () => ({ captureSnapshotWithInteractor: vi.fn(async () => { @@ -22,7 +23,8 @@ test('test --json marks a typed live device claim as infrastructure without retr let attempts = 0; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -70,7 +72,8 @@ test('test --json retries DEVICE_IN_USE without typed device-claim provenance', let attempts = 0; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/__tests__/replay-suite/session-test-suite.test.ts b/src/daemon/__tests__/replay-suite/session-test-suite.test.ts index 3d36505d3a..139bff496b 100644 --- a/src/daemon/__tests__/replay-suite/session-test-suite.test.ts +++ b/src/daemon/__tests__/replay-suite/session-test-suite.test.ts @@ -1,6 +1,7 @@ import { daemonPlatformServicesFixture } from '../platform-services-fixture.ts'; import type { RequestProgressEvent } from '@agent-device/contracts/progress'; import { test, expect, vi } from 'vitest'; +import { testCreateCommandSurface } from '../command-surface-fixture.ts'; // ADR 0012 migration step 2: every replay step failure now attempts a // post-failure screen digest capture + suggestion re-resolution through the @@ -64,7 +65,8 @@ test('test does not retry infrastructure startup failures and stops the suite', const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -112,7 +114,8 @@ test('test --fail-fast stops the suite after the first failure and leaves the re const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -159,7 +162,8 @@ test('test surfaces a suite-level failure when a source fails to parse', async ( const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -194,7 +198,8 @@ test('test discovers Maestro YAML suites when replay backend is set', async () = const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -230,7 +235,8 @@ test('test emits progress when attempts retry and pass', async () => { (event) => events.push(event), async () => await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -338,7 +344,8 @@ test('test stops before retrying when a rejected close leaves the prior macOS se let replayAttempts = 0; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -390,7 +397,8 @@ test('test stops retrying after maxAttempts when every attempt fails', async () let attemptCount = 0; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -434,7 +442,8 @@ test('test emits skip progress without synthetic duration', async () => { (event) => events.push(event), async () => await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -487,7 +496,8 @@ test('test aggregates snapshot diagnostics from replay session samples', async ( let captures = 0; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -546,7 +556,8 @@ test('test aggregates snapshot diagnostics from failed replay session samples', fs.writeFileSync(path.join(root, '01-fail.ad'), 'context platform=android\nopen "Demo"\n'); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -619,7 +630,8 @@ test('test stops the suite when the parent request is canceled during an active (event) => events.push(event), async () => await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -685,7 +697,8 @@ test('test --shard-all runs each runnable entry on each selected device', async async () => [ANDROID_ONE, ANDROID_TWO], async () => await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -761,7 +774,8 @@ test('test --shard-split distributes runnable entries by modulo and keeps skips async () => [ANDROID_TWO, ANDROID_ONE], async () => await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -810,7 +824,8 @@ test('test sharding rejects mutually exclusive shard modes', async () => { fs.writeFileSync(path.join(root, '01-a.ad'), 'context platform=android\nopen "Demo"\n'); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -837,7 +852,8 @@ test('test sharding rejects non-positive shard counts', async () => { fs.writeFileSync(path.join(root, '01-a.ad'), 'context platform=android\nopen "Demo"\n'); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -867,7 +883,8 @@ test('test sharding rejects fewer matched devices than requested shards', async async () => [ANDROID_ONE], async () => await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -902,7 +919,8 @@ test('test sharding does not require devices when every entry is skipped', async }, async () => await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/__tests__/request-dispatch-disclosure.test.ts b/src/daemon/__tests__/request-dispatch-disclosure.test.ts index 676c04ea48..f5f31f0cd4 100644 --- a/src/daemon/__tests__/request-dispatch-disclosure.test.ts +++ b/src/daemon/__tests__/request-dispatch-disclosure.test.ts @@ -2,6 +2,7 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { beforeEach, test, vi } from 'vitest'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; vi.mock('@agent-device/platform-apple/runner/operations', async (importOriginal) => { const actual = @@ -124,6 +125,7 @@ async function route( const sessionStore = makeSessionStore('agent-device-dispatch-route-'); sessionStore.publish(SESSION, session); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/request-handler-catalog.test.ts b/src/daemon/__tests__/request-handler-catalog.test.ts index c9b3ae490f..dff4981887 100644 --- a/src/daemon/__tests__/request-handler-catalog.test.ts +++ b/src/daemon/__tests__/request-handler-catalog.test.ts @@ -29,6 +29,7 @@ import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; import { createAudioProbeAdmissionLedger } from '@agent-device/capture-kit/audio-probe-admission-ledger'; import { createPerfCaptureAdmissionLedger } from '@agent-device/capture-kit/perf-capture-admission-ledger'; import { createScreenRecordingAdmissionLedger } from '@agent-device/capture-kit/screen-recording-admission-ledger'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; const SPECIALIZED_ROUTES = [ 'humanControl', @@ -507,7 +508,8 @@ async function runCatalogCommandThroughHandlerChain( async () => [], async () => await runRequestHandlerChain({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req, sessionName: req.session, logPath: '/tmp/agent-device-catalog-route.log', diff --git a/src/daemon/__tests__/request-handler-chain-provider-scope.test.ts b/src/daemon/__tests__/request-handler-chain-provider-scope.test.ts index 6795bd9417..98d3f7e9bf 100644 --- a/src/daemon/__tests__/request-handler-chain-provider-scope.test.ts +++ b/src/daemon/__tests__/request-handler-chain-provider-scope.test.ts @@ -3,6 +3,7 @@ import { daemonPlatformServicesFixture } from './platform-services-fixture.ts'; // through the neutral providerScope, and that an empty scope forwards none. import assert from 'node:assert/strict'; import { test, vi } from 'vitest'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; const handleSessionCommandsMock = vi.fn(async (_params: unknown) => ({ ok: true, data: {} })); vi.mock('../handlers/session.ts', () => ({ @@ -76,7 +77,8 @@ test('the android adb executor from the generic provider scope reaches the sessi }); await runRequestHandlerChain({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), ...baseChainParams('provider-scope-test'), providerScope: { androidAdbExecutor }, }); @@ -92,7 +94,8 @@ test('an empty provider scope forwards no android adb executor to the session ha handleSessionCommandsMock.mockClear(); await runRequestHandlerChain({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), ...baseChainParams('provider-scope-empty'), providerScope: {}, }); diff --git a/src/daemon/__tests__/request-handler-chain.test.ts b/src/daemon/__tests__/request-handler-chain.test.ts index 11345712c9..86d8223fd5 100644 --- a/src/daemon/__tests__/request-handler-chain.test.ts +++ b/src/daemon/__tests__/request-handler-chain.test.ts @@ -27,6 +27,7 @@ import { createAudioProbeAdmissionLedger } from '@agent-device/capture-kit/audio import { createPerfCaptureAdmissionLedger } from '@agent-device/capture-kit/perf-capture-admission-ledger'; import { createScreenRecordingAdmissionLedger } from '@agent-device/capture-kit/screen-recording-admission-ledger'; import { eagerClosureOf } from '../../__tests__/eager-import-closure.fixtures.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; function makeRequest(command: string, positionals: string[] = []): DaemonRequest { return { @@ -43,6 +44,7 @@ function makeChainParams(req: DaemonRequest) { const sessionStore = makeSessionStore('agent-device-request-chain-'); sessionStore.publish('chain-test', makeIosSession('chain-test')); return { + createCommandSurface: testCreateCommandSurface, req, sessionName: 'chain-test', logPath: '/tmp/agent-device-request-chain.log', @@ -312,6 +314,7 @@ test('duration-less public coordinate swipe retains Linux drag behavior', async provider, async () => await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, platformServices: daemonPlatformServicesFixture(), inspectFacts: bindings.inspectFacts, bindDevice: bindings.bindDevice, diff --git a/src/daemon/__tests__/request-router-android-modal.test.ts b/src/daemon/__tests__/request-router-android-modal.test.ts index 0320d46bc9..a6ccbb85d3 100644 --- a/src/daemon/__tests__/request-router-android-modal.test.ts +++ b/src/daemon/__tests__/request-router-android-modal.test.ts @@ -4,6 +4,7 @@ import { test, expect, vi } from 'vitest'; import path from 'node:path'; import { AppError } from '@agent-device/kernel/errors'; import type { RawSnapshotNode } from '@agent-device/kernel/snapshot'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; let snapshotCalls = 0; let snapshotMode: 'blocking-dialog' | 'throws' = 'blocking-dialog'; @@ -156,6 +157,7 @@ test('generic Android gesture commands dismiss blocking system dialogs during re const { openAndroidApp } = await import('@agent-device/platform-android/mechanics'); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -208,6 +210,7 @@ test('generic Android gesture commands continue when recording dialog inspection vi.mocked(openAndroidApp).mockClear(); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -267,6 +270,7 @@ test('generic Android gesture commands skip local dialog recovery for provider d const providers = createProviderDeviceRuntimeRequestProviders([runtime]); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/request-router-android-perf.test.ts b/src/daemon/__tests__/request-router-android-perf.test.ts index 21608bbb8e..35f4786cf8 100644 --- a/src/daemon/__tests__/request-router-android-perf.test.ts +++ b/src/daemon/__tests__/request-router-android-perf.test.ts @@ -12,6 +12,7 @@ import { createPlatformRuntimeGateway, createRequestPlatformProviders, } from '../../platform-runtime.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; function makeAndroidSessionStore(name: string): SessionStore { const sessionStore = new SessionStore(`/tmp/${name}`); @@ -40,6 +41,7 @@ function makeHandler(sessionStore: SessionStore, androidAdbProvider: () => Andro }), }); return createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: '/tmp/daemon.log', token: 'token', sessionStore, diff --git a/src/daemon/__tests__/request-router-android-snapshot-helper.test.ts b/src/daemon/__tests__/request-router-android-snapshot-helper.test.ts index 3bb4b23ce4..f118f90cc8 100644 --- a/src/daemon/__tests__/request-router-android-snapshot-helper.test.ts +++ b/src/daemon/__tests__/request-router-android-snapshot-helper.test.ts @@ -11,6 +11,7 @@ import { createPlatformRuntimeGateway, createRequestPlatformProviders, } from '../../platform-runtime.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; function makeAndroidSessionStore(name: string): SessionStore { const sessionStore = new SessionStore(`/tmp/${name}`); @@ -39,6 +40,7 @@ function makeHandler(sessionStore: SessionStore, androidAdbProvider: () => Andro }), }); return createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: '/tmp/daemon.log', token: 'token', sessionStore, diff --git a/src/daemon/__tests__/request-router-apps-admission.test.ts b/src/daemon/__tests__/request-router-apps-admission.test.ts index 4602b1ed69..2d1190efb1 100644 --- a/src/daemon/__tests__/request-router-apps-admission.test.ts +++ b/src/daemon/__tests__/request-router-apps-admission.test.ts @@ -9,6 +9,7 @@ import { LeaseRegistry } from '../lease-registry.ts'; import type { DaemonRequest } from '../daemon-request.ts'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; function createAppsAdmissionHarness(apps: readonly string[] = []) { const listProviderApps = vi.fn(async () => apps); @@ -23,6 +24,7 @@ function createAppsAdmissionHarness(apps: readonly string[] = []) { throw new Error('apps catalog must not bind a device'); }); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore: makeSessionStore('agent-device-apps-admission-'), diff --git a/src/daemon/__tests__/request-router-artifacts-web.test.ts b/src/daemon/__tests__/request-router-artifacts-web.test.ts index 0923d10c49..7e31efbc25 100644 --- a/src/daemon/__tests__/request-router-artifacts-web.test.ts +++ b/src/daemon/__tests__/request-router-artifacts-web.test.ts @@ -12,6 +12,7 @@ import { WEB_DESKTOP_DEVICE } from '../../__tests__/test-utils/device-fixtures.t import { cleanupDownloadableArtifact, trackDownloadableArtifact } from '../artifact-tracking.ts'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; // #1900: `artifacts` (`handlers/lease.ts`) lists daemon-tracked artifacts by tenant scope with no // device or platform involvement at all — `listArtifactsForRequest` never reads `device.platform`. @@ -31,6 +32,7 @@ test('artifacts lists a daemon-tracked artifact produced during a web session', try { const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/request-router-cost.test.ts b/src/daemon/__tests__/request-router-cost.test.ts index df934bbb85..7db2497e02 100644 --- a/src/daemon/__tests__/request-router-cost.test.ts +++ b/src/daemon/__tests__/request-router-cost.test.ts @@ -2,6 +2,7 @@ import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/de import { test, expect, vi, beforeEach } from 'vitest'; import path from 'node:path'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; vi.mock('@agent-device/platform-apple/runner/operations', async (importOriginal) => { const actual = @@ -54,6 +55,7 @@ function makeHandler(sessionStore = makeSessionStore('agent-device-router-cost-' return { sessionStore, handler: createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/request-router-custom-action-flags.test.ts b/src/daemon/__tests__/request-router-custom-action-flags.test.ts index 40e94baece..5cd5582c6f 100644 --- a/src/daemon/__tests__/request-router-custom-action-flags.test.ts +++ b/src/daemon/__tests__/request-router-custom-action-flags.test.ts @@ -13,9 +13,11 @@ import { createRequestHandler } from './test-device-runtime-gateway.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; function createHandler() { return createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore: makeSessionStore('agent-device-router-custom-action-flags-'), diff --git a/src/daemon/__tests__/request-router-developer-dir.test.ts b/src/daemon/__tests__/request-router-developer-dir.test.ts index 77314bd2a0..2c225eedbf 100644 --- a/src/daemon/__tests__/request-router-developer-dir.test.ts +++ b/src/daemon/__tests__/request-router-developer-dir.test.ts @@ -6,10 +6,12 @@ import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; test.sequential('a request spawns its commands with its own DEVELOPER_DIR, else the daemon one', async () => { const seen: string[] = []; const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('agent-device-router-developer-dir-'), 'daemon.log'), token: 'test-token', sessionStore: makeSessionStore('agent-device-router-developer-dir-store-'), diff --git a/src/daemon/__tests__/request-router-error-redaction.test.ts b/src/daemon/__tests__/request-router-error-redaction.test.ts index a4238d4cfe..0e73d47544 100644 --- a/src/daemon/__tests__/request-router-error-redaction.test.ts +++ b/src/daemon/__tests__/request-router-error-redaction.test.ts @@ -22,6 +22,7 @@ import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/de import { makeIosAppSession } from '../../__tests__/test-utils/session-factories.ts'; import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import { createRequestHandler, @@ -61,6 +62,7 @@ test('a Maestro replay error comes back unchanged after its inputText registered const flowPath = path.join(root, 'flow.yaml'); fs.writeFileSync(flowPath, 'appId: com.example.app\n---\n- inputText: "1"\n'); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(root, 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/request-router-events.test.ts b/src/daemon/__tests__/request-router-events.test.ts index 1c4e7a8d32..908c06eb7f 100644 --- a/src/daemon/__tests__/request-router-events.test.ts +++ b/src/daemon/__tests__/request-router-events.test.ts @@ -9,6 +9,7 @@ import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import { makeIosSession, makeSession } from '../../__tests__/test-utils/session-factories.ts'; import { WEB_DESKTOP_DEVICE } from '../../__tests__/test-utils/device-fixtures.ts'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; test('events reads the daemon-owned session timeline without appending poll noise', async () => { const sessionStore = makeSessionStore('agent-device-router-events-'); @@ -21,6 +22,7 @@ test('events reads the daemon-owned session timeline without appending poll nois const eventLogPath = sessionStore.resolveEventLogPath('events-session'); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -65,6 +67,7 @@ test('events accepts a blank limit placeholder for cursor-only reads', async () }); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -104,6 +107,7 @@ test('events returns structured errors for invalid limit and cursor', async () = }); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -151,6 +155,7 @@ test('events flushes pending event writes before reading', async () => { }); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -188,6 +193,7 @@ test('events reads the daemon-owned session timeline for a web-backed session', }); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -221,6 +227,7 @@ test('request timeline records thrown request failures after scope creation', as sessionStore.publish('events-session', makeIosSession('events-session')); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -260,6 +267,7 @@ test('request timeline records setup failures after start is appended', async () const sessionStore = makeSessionStore('agent-device-router-events-setup-failure-'); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/request-router-fill-text-stdin.test.ts b/src/daemon/__tests__/request-router-fill-text-stdin.test.ts index ae83c55064..2dccdab7c2 100644 --- a/src/daemon/__tests__/request-router-fill-text-stdin.test.ts +++ b/src/daemon/__tests__/request-router-fill-text-stdin.test.ts @@ -17,6 +17,7 @@ import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import { makeAuthoringSession, makeSession } from '../../__tests__/test-utils/session-factories.ts'; import { WEB_DESKTOP_DEVICE } from '../../__tests__/test-utils/device-fixtures.ts'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; import { replayScriptSourceBundleFor } from '../../__tests__/test-utils/replay-script-source.ts'; import { createPlatformRuntimeGateway, @@ -62,6 +63,7 @@ function sendToWebSession( }; const dir = mkdtempForTestSync('agent-device-router-fill-text-stdin-runtime-'); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(dir, 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/request-router-idle-expired.test.ts b/src/daemon/__tests__/request-router-idle-expired.test.ts index b69d2f9fa6..fc86468602 100644 --- a/src/daemon/__tests__/request-router-idle-expired.test.ts +++ b/src/daemon/__tests__/request-router-idle-expired.test.ts @@ -15,10 +15,12 @@ import type { DaemonRequest } from '../daemon-request.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; function makeHandler(prefix: string) { const sessionStore = makeSessionStore(prefix); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/request-router-lock-policy.test.ts b/src/daemon/__tests__/request-router-lock-policy.test.ts index d1debf25b5..33252ee141 100644 --- a/src/daemon/__tests__/request-router-lock-policy.test.ts +++ b/src/daemon/__tests__/request-router-lock-policy.test.ts @@ -5,6 +5,7 @@ import { import { test, expect, vi, beforeEach } from 'vitest'; import { legacyDispatchCapture } from './legacy-snapshot-capture-fixture.ts'; import path from 'node:path'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; vi.mock('@agent-device/platform-apple/runner/operations', async (importOriginal) => { const actual = @@ -125,6 +126,7 @@ test('direct daemon requests cannot bypass reject lock policy for existing sessi sessionStore.publish('qa-ios', makeIosSession('qa-ios')); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -161,6 +163,7 @@ test('fresh named sessions with matching explicit serial bind and serialize on t const dispatchGate = installGatedDispatch(); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -239,6 +242,7 @@ test('fresh named sessions with the same name serialize first binding before rej const dispatchGate = installGatedDispatch(); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -312,6 +316,7 @@ test('fresh named sessions with only lock platform default serialize on the sele const dispatchGate = installGatedDispatch(); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -420,6 +425,7 @@ test('fresh named sessions reject incompatible selector combinations before bind for (const testCase of cases) { const sessionStore = makeSessionStore('agent-device-router-lock-'); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -458,6 +464,7 @@ test('batch steps cannot bypass reject lock policy on nested direct requests', a sessionStore.publish('qa-ios', makeIosSession('qa-ios')); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -503,6 +510,7 @@ test('direct daemon requests apply strip lock policy for existing sessions befor systemRuntimeSpies.appSwitcher.mockClear(); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -546,6 +554,7 @@ test('strip lock policy still refuses a request naming a different device, befor }); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -584,6 +593,7 @@ test('batch preserves tenant-scoped session names across nested requests', async systemRuntimeSpies.appSwitcher.mockClear(); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/request-router-macos-app-lease.test.ts b/src/daemon/__tests__/request-router-macos-app-lease.test.ts index b0e37c3063..95532a793f 100644 --- a/src/daemon/__tests__/request-router-macos-app-lease.test.ts +++ b/src/daemon/__tests__/request-router-macos-app-lease.test.ts @@ -9,6 +9,7 @@ import type { DaemonRequest, DaemonResponse } from '../daemon-request.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; import { screenshotRuntimeFixture } from './screenshot-runtime-fixture.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; const BUNDLE_ID = 'com.example.leased'; const LEASE_ID = 'a1b2c3d4e5f60718293a4b5c6d7e8f90'; @@ -52,6 +53,7 @@ function leasedRouter() { }); const runtime = screenshotRuntimeFixture(); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -119,6 +121,7 @@ test('a leased snapshot carries the app tree and nothing about the host', async test('a request without a macos-app lease keeps its log locators', async () => { const sessionStore = makeSessionStore('agent-device-router-unleased-'); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/request-router-open-claim.test.ts b/src/daemon/__tests__/request-router-open-claim.test.ts index eabd859560..18c41a8aca 100644 --- a/src/daemon/__tests__/request-router-open-claim.test.ts +++ b/src/daemon/__tests__/request-router-open-claim.test.ts @@ -5,6 +5,7 @@ import fs from 'node:fs'; import path from 'node:path'; import crypto from 'node:crypto'; import { getResolveTargetDeviceMock } from './request-router-dispatch-mocks.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; vi.mock('../device/device-ready.ts', () => ({ ensureDeviceReady: vi.fn(async () => {}) })); vi.mock('@agent-device/host-kit/process', async (importOriginal) => { @@ -82,6 +83,7 @@ function createOpenHandler( leaseRegistry = new LeaseRegistry(), ) { return createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/request-router-open.test.ts b/src/daemon/__tests__/request-router-open.test.ts index 443eadd381..2e1afb0d05 100644 --- a/src/daemon/__tests__/request-router-open.test.ts +++ b/src/daemon/__tests__/request-router-open.test.ts @@ -11,6 +11,7 @@ import path from 'node:path'; import { getResolveTargetDeviceMock } from './request-router-dispatch-mocks.ts'; import { replayScriptSourceBundleFor } from '../../__tests__/test-utils/replay-script-source.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; vi.mock('../device/device-ready.ts', () => ({ ensureDeviceReady: vi.fn(async () => {}) })); vi.mock('@agent-device/host-kit/process', async (importOriginal) => { @@ -120,6 +121,7 @@ function createOpenHandler( leaseRegistry = new LeaseRegistry(), ) { return createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -492,6 +494,7 @@ test('close fails synchronously when root composition omits platform resource cl actions: [], }); const handler = createProductionRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), platformServices: daemonPlatformServicesFixture(), claimRecoveryGateway: daemonClaimRecoveryGatewayFixture(), diff --git a/src/daemon/__tests__/request-router-provider-credentials.test.ts b/src/daemon/__tests__/request-router-provider-credentials.test.ts index 53b0d20a35..b560fa3d1e 100644 --- a/src/daemon/__tests__/request-router-provider-credentials.test.ts +++ b/src/daemon/__tests__/request-router-provider-credentials.test.ts @@ -9,6 +9,7 @@ import { } from '../../provider-credential-fingerprint.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; const DAEMON_ENV = { LIMRUN_API_KEY: 'lim-key' }; @@ -21,6 +22,7 @@ test.for([ const stateDir = mkdtempForTestSync('agent-device-router-provider-credentials-'); let allocated = 0; const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(stateDir, 'daemon.log'), token: 'test-token', sessionStore: makeSessionStore('agent-device-router-provider-credentials-store-'), diff --git a/src/daemon/__tests__/request-router-record-flags.test.ts b/src/daemon/__tests__/request-router-record-flags.test.ts index 4c9c46ccae..d12d741889 100644 --- a/src/daemon/__tests__/request-router-record-flags.test.ts +++ b/src/daemon/__tests__/request-router-record-flags.test.ts @@ -13,9 +13,11 @@ import { createRequestHandler } from './test-device-runtime-gateway.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; function createHandler() { return createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore: makeSessionStore('agent-device-router-record-flags-'), diff --git a/src/daemon/__tests__/request-router-record-runtime-lock.test.ts b/src/daemon/__tests__/request-router-record-runtime-lock.test.ts index 942ee36e1c..47b7960b88 100644 --- a/src/daemon/__tests__/request-router-record-runtime-lock.test.ts +++ b/src/daemon/__tests__/request-router-record-runtime-lock.test.ts @@ -17,6 +17,7 @@ import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import { createPlatformRuntimeGateway } from '../../platform-runtime.ts'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; const DEVICE: DeviceInfo = { platform: 'android', @@ -41,6 +42,7 @@ test('fresh default-device recording starts serialize before durable admission', const runtime = makeRecordingGateway(firstStartBlocked); const sessionStore = makeSessionStore('request-router-record-runtime-lock-'); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: '/tmp/daemon.log', token: 'token', sessionStore, @@ -91,6 +93,7 @@ test.each([ }); const bind = vi.fn(async (request) => await gateway.bind(request)); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: '/tmp/daemon.log', token: 'token', sessionStore, diff --git a/src/daemon/__tests__/request-router-recording-health.test.ts b/src/daemon/__tests__/request-router-recording-health.test.ts index c521fda2b3..b00b7ccd3b 100644 --- a/src/daemon/__tests__/request-router-recording-health.test.ts +++ b/src/daemon/__tests__/request-router-recording-health.test.ts @@ -3,6 +3,7 @@ import { legacyDispatchCapture } from './legacy-snapshot-capture-fixture.ts'; import { test, expect, vi, beforeEach } from 'vitest'; import path from 'node:path'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; import { daemonPlatformServicesFixture } from './platform-services-fixture.ts'; import { @@ -59,6 +60,7 @@ test('router blocks non-record commands when recording was invalidated', async ( sessionStore.publish('default', session); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -114,6 +116,7 @@ test('router allows canonical iOS simulator gestures during overlay recording af sessionId: 'runner-after', }); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/request-router-repair-expired.test.ts b/src/daemon/__tests__/request-router-repair-expired.test.ts index 6b768101e8..29675951cb 100644 --- a/src/daemon/__tests__/request-router-repair-expired.test.ts +++ b/src/daemon/__tests__/request-router-repair-expired.test.ts @@ -11,6 +11,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { getResolveTargetDeviceMock } from './request-router-dispatch-mocks.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; vi.mock('../device/device-ready.ts', () => ({ ensureDeviceReady: vi.fn(async () => {}) })); @@ -30,6 +31,7 @@ const mockResolveTargetDevice = vi.mocked(getResolveTargetDeviceMock()); function makeHandler(prefix: string) { const sessionStore = makeSessionStore(prefix); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/request-router-replay-env.test.ts b/src/daemon/__tests__/request-router-replay-env.test.ts index 8292ee6592..3d8ca2dc7e 100644 --- a/src/daemon/__tests__/request-router-replay-env.test.ts +++ b/src/daemon/__tests__/request-router-replay-env.test.ts @@ -7,12 +7,14 @@ import { LeaseRegistry } from '../lease-registry.ts'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; import { replayScriptSourceBundleFor } from '../../__tests__/test-utils/replay-script-source.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; function createHarness() { const root = mkdtempForTestSync('agent-device-router-replay-env-'); return { root, handler: createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(root, 'daemon.log'), token: 'test-token', sessionStore: makeSessionStore('agent-device-router-replay-env-store-'), diff --git a/src/daemon/__tests__/request-router-replay-scope.test.ts b/src/daemon/__tests__/request-router-replay-scope.test.ts index 9e1bf571a6..8a71151df9 100644 --- a/src/daemon/__tests__/request-router-replay-scope.test.ts +++ b/src/daemon/__tests__/request-router-replay-scope.test.ts @@ -5,6 +5,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { getResolveTargetDeviceMock } from './request-router-dispatch-mocks.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; vi.mock('../device/device-ready.ts', () => ({ ensureDeviceReady: vi.fn(async () => {}) })); @@ -71,6 +72,7 @@ test('replay runs active-session actions inside the parent request provider scop const appleRunnerProvider = vi.fn(() => undefined); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -109,6 +111,7 @@ test('replay routes session-changing actions through the full request path', asy const appleRunnerProvider = vi.fn(() => undefined); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -144,6 +147,7 @@ test('session list includes a cwd-scoped session opened by replay', async () => const sessionStore = makeSessionStore('agent-device-replay-open-scope-'); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -210,6 +214,7 @@ test('fresh replay retains a dynamically selected device through finalization', }); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/request-router-replay-test-ime.test.ts b/src/daemon/__tests__/request-router-replay-test-ime.test.ts index 46d9b70a07..11f1935cf2 100644 --- a/src/daemon/__tests__/request-router-replay-test-ime.test.ts +++ b/src/daemon/__tests__/request-router-replay-test-ime.test.ts @@ -11,6 +11,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { getResolveTargetDeviceMock } from './request-router-dispatch-mocks.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; vi.mock('../device/device-ready.ts', () => ({ ensureDeviceReady: vi.fn(async () => {}) })); @@ -62,6 +63,7 @@ async function runAndroidFlowReplay(session: string, flags: Record { const actual = @@ -68,6 +69,7 @@ function makeHandler() { return { sessionStore, handler: createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/request-router-screenshot.test.ts b/src/daemon/__tests__/request-router-screenshot.test.ts index db017bd1cd..8c47ddd009 100644 --- a/src/daemon/__tests__/request-router-screenshot.test.ts +++ b/src/daemon/__tests__/request-router-screenshot.test.ts @@ -4,6 +4,7 @@ import { test, expect, vi, beforeEach } from 'vitest'; import fs from 'node:fs'; import path from 'node:path'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; // `scroll` still executes through legacy platform dispatch; screenshot and click bind their fake // at the facts/bind seam below instead (ADR 0019). @@ -78,6 +79,7 @@ function screenshotRouter( sessionStore.publish(session.name, session); const runtime = screenshotRuntimeFixture(options); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, @@ -236,6 +238,7 @@ test('router serializes concurrent commands for the same device across sessions' }); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/request-router-session-address.test.ts b/src/daemon/__tests__/request-router-session-address.test.ts index 4dfdffa237..95a6fa26ad 100644 --- a/src/daemon/__tests__/request-router-session-address.test.ts +++ b/src/daemon/__tests__/request-router-session-address.test.ts @@ -4,6 +4,7 @@ import { test, expect, vi, beforeEach } from 'vitest'; import path from 'node:path'; import { getResolveTargetDeviceMock } from './request-router-dispatch-mocks.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; vi.mock('../device/device-ready.ts', () => ({ ensureDeviceReady: vi.fn(async () => {}) })); vi.mock('@agent-device/host-kit/process', async (importOriginal) => { @@ -61,6 +62,7 @@ function makeIosDevice(id: string): DeviceInfo { function createHandler(sessionStore: ReturnType) { return createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/request-router-typed-error.test.ts b/src/daemon/__tests__/request-router-typed-error.test.ts index 3108eaec9f..6a86e18c52 100644 --- a/src/daemon/__tests__/request-router-typed-error.test.ts +++ b/src/daemon/__tests__/request-router-typed-error.test.ts @@ -4,6 +4,7 @@ import { test, expect, vi, beforeEach } from 'vitest'; import fs from 'node:fs'; import path from 'node:path'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; vi.mock('@agent-device/platform-apple/runner/operations', async (importOriginal) => { const actual = @@ -64,6 +65,7 @@ function makeHandler(sessionStore = makeSessionStore('agent-device-router-typed- return { sessionStore, handler: createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/request-runtime-binding-router.test.ts b/src/daemon/__tests__/request-runtime-binding-router.test.ts index ecb28d7b19..7343f9f326 100644 --- a/src/daemon/__tests__/request-runtime-binding-router.test.ts +++ b/src/daemon/__tests__/request-runtime-binding-router.test.ts @@ -14,6 +14,7 @@ import { unavailableDeploymentSnapshotAndShutdownOperationFacts } from '../../__ import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; test('request binding disposes once after response while adopted app-log handle survives', async () => { const runtime = makeGateway(); @@ -60,6 +61,7 @@ function makeHandler(gateway: DeviceRuntimeGateway) { return { sessionStore, handler: createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: '/tmp/daemon.log', token: 'token', sessionStore, diff --git a/src/daemon/__tests__/request-save-script-transports.test.ts b/src/daemon/__tests__/request-save-script-transports.test.ts index 1dd4273295..1f9542214a 100644 --- a/src/daemon/__tests__/request-save-script-transports.test.ts +++ b/src/daemon/__tests__/request-save-script-transports.test.ts @@ -34,6 +34,7 @@ import { } from '../../__tests__/test-utils/loopback.ts'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; import { flushSessionEventLogWrites } from '@agent-device/session-journal/session-event-log'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; const TOKEN = 'save-script-transport-token'; const SESSION = 'save-script-transport'; @@ -70,6 +71,7 @@ function setup(): Harness { const session = makeIosSession(SESSION); sessionStore.publish(SESSION, session); const handleRequest = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(root, 'daemon.log'), token: TOKEN, sessionStore, diff --git a/src/daemon/__tests__/runtime-binding-conformance.ts b/src/daemon/__tests__/runtime-binding-conformance.ts index 3cbfb8673f..1ff6371849 100644 --- a/src/daemon/__tests__/runtime-binding-conformance.ts +++ b/src/daemon/__tests__/runtime-binding-conformance.ts @@ -36,6 +36,7 @@ import { resolveBoundTvRemoteRuntime } from '../tv-remote-runtime.ts'; import { resolveBoundTypeTextRuntime } from '../type-text-runtime.ts'; import { resolveBoundViewportRuntime } from '../viewport-runtime.ts'; import type { DaemonFailureResponse } from '@agent-device/kernel/contracts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; type RuntimeOperation = RuntimeOperationKey; @@ -151,6 +152,7 @@ export const conformedRuntimeBindings = { const session = makeSession('diff-runtime', { device, appBundleId: 'com.example.app' }); sessionStore.publish(session.name, session); const response = await dispatchSnapshotDiffViaRuntime({ + createCommandSurface: testCreateCommandSurface, req: { command: 'diff', positionals: ['snapshot'], diff --git a/src/daemon/__tests__/screenshot-runtime.test.ts b/src/daemon/__tests__/screenshot-runtime.test.ts index 776a346381..9890107bd0 100644 --- a/src/daemon/__tests__/screenshot-runtime.test.ts +++ b/src/daemon/__tests__/screenshot-runtime.test.ts @@ -13,6 +13,7 @@ import { screenshotRuntimeFixture, writeSolidPng } from './screenshot-runtime-fi import type { DaemonRequest } from '../daemon-request.ts'; import type { SessionState } from '../session-state.ts'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; const unavailableCapture = Object.freeze({ available: false, @@ -69,7 +70,8 @@ test('admits one capture plan, binds once, and hands the runtime the resolved de const req = screenshotRequest({ positionals: [outPath] }); const resolved = await resolveScreenshotGenericExecution({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req, session, inspectFacts: fixture.inspectFacts, @@ -95,7 +97,8 @@ test('an iOS simulator session capture skips the redundant boot probe', async () const req = screenshotRequest({ positionals: [outPath] }); const resolved = await resolveScreenshotGenericExecution({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req, session, inspectFacts: fixture.inspectFacts, @@ -114,7 +117,8 @@ test('refuses an unavailable exact-owner capture fact before binding', async () const session = makeSession('default', { device: ANDROID_EMULATOR }); const resolved = await resolveScreenshotGenericExecution({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: screenshotRequest({ positionals: ['/tmp/unsupported.png'] }), session, inspectFacts: fixture.inspectFacts, @@ -144,7 +148,8 @@ test('--overlay-refs is refused up front when the target cannot capture a tree', const session = makeSession('default', { device: ANDROID_EMULATOR }); const resolved = await resolveScreenshotGenericExecution({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: screenshotRequest({ positionals: ['/tmp/overlay.png'], flags: { overlayRefs: true } }), session, inspectFacts: fixture.inspectFacts, @@ -177,7 +182,8 @@ test('the default destination is a reserved temp file, removed when the capture const req = screenshotRequest(); const resolved = await resolveScreenshotGenericExecution({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req, session, inspectFacts: fixture.inspectFacts, @@ -196,7 +202,8 @@ test('rejects the retired max-size field before inspecting owner facts', async ( await expect( resolveScreenshotGenericExecution({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: screenshotRequest({ positionals: ['/tmp/legacy.png'], flags: { screenshotMaxSize: 720 } as unknown as DaemonRequest['flags'], @@ -215,7 +222,8 @@ test('rejects --pixel-density outside iOS-family simulators before inspecting ow await expect( resolveScreenshotGenericExecution({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: screenshotRequest({ positionals: ['/tmp/android.png'], flags: { screenshotPixelDensity: 2 }, @@ -266,7 +274,8 @@ test('the crop runs after the platform write and before the shared scale', async }); const resolved = await resolveScreenshotGenericExecution({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req, session, inspectFacts: fixture.inspectFacts, @@ -315,7 +324,8 @@ test('a partial crop surfaces its warning once in the result record and annotate }); const resolved = await resolveScreenshotGenericExecution({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req, session, inspectFacts: fixture.inspectFacts, @@ -338,7 +348,8 @@ test('the crop plan is refused up front when the target cannot capture a tree', const session = makeSession('default', { device: ANDROID_EMULATOR }); const resolved = await resolveScreenshotGenericExecution({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: screenshotRequest({ positionals: ['/tmp/crop.png'], flags: { screenshotCropOn: 'label="Save"' }, @@ -374,7 +385,8 @@ test('the result record carries the display rotation the capture reported', asyn const req = screenshotRequest({ positionals: [outPath] }); const resolved = await resolveScreenshotGenericExecution({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req, session, inspectFacts: fixture.inspectFacts, @@ -394,7 +406,8 @@ test('the result record omits the display rotation when the capture reported non const req = screenshotRequest({ positionals: [outPath] }); const resolved = await resolveScreenshotGenericExecution({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req, session, inspectFacts: fixture.inspectFacts, diff --git a/src/daemon/__tests__/selector-capture-binding.test.ts b/src/daemon/__tests__/selector-capture-binding.test.ts index 64865648d8..f065a07f4a 100644 --- a/src/daemon/__tests__/selector-capture-binding.test.ts +++ b/src/daemon/__tests__/selector-capture-binding.test.ts @@ -10,6 +10,7 @@ import { withTestDeviceInventory } from '../../__tests__/test-utils/device-inven import { resolveBoundSelectorCapture } from '../selector-capture-binding.ts'; import { createBoundSelectorRuntime } from '../selector-runtime-backend.ts'; import { selectorCaptureFixture } from './selector-capture-fixture.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; // The seam every selector unit consumes. `find` landed it; `get`, `is`, and `wait` migrate by // naming their command, so these are the guarantees they inherit rather than re-derive. @@ -146,7 +147,8 @@ test('the bound construction path admits and binds before it builds a runtime', const resolved = await createBoundSelectorRuntime( { - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'bound', command: 'get', positionals: [], flags: {} }, sessionName: 'bound', logPath: '/tmp/bound.log', @@ -171,7 +173,8 @@ test('the bound construction path refuses an unavailable operation without build const resolved = await createBoundSelectorRuntime( { - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'bound', command: 'is', positionals: [], flags: {} }, sessionName: 'bound', logPath: '/tmp/bound.log', diff --git a/src/daemon/__tests__/selector-runtime-ambiguity-issuance.test.ts b/src/daemon/__tests__/selector-runtime-ambiguity-issuance.test.ts index 11044b97f2..5227d1b11d 100644 --- a/src/daemon/__tests__/selector-runtime-ambiguity-issuance.test.ts +++ b/src/daemon/__tests__/selector-runtime-ambiguity-issuance.test.ts @@ -18,6 +18,7 @@ import { readPressPoint, } from '../interaction/internal/__tests__/interaction-touch-fixtures.ts'; import { handleInteractionCommands } from '../interaction/index.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; // #2870 review, the reviewer's pinned invariant: a response that prints // candidate @refs must issue those refs on the frame they came from. The end- @@ -110,7 +111,8 @@ test('an ambiguous is issues its printed candidates so press acts on the listed const fixture = selectorCaptureFixture({ snapshot: () => ambiguousFullCapture() }); const response = await dispatchIsViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: isRequest(sessionName, ['visible', 'label="Deploy"']), sessionName, sessionStore, @@ -136,7 +138,8 @@ test('an ambiguous is issues its printed candidates so press acts on the listed // (10,20) button the same body named before. const pinned = candidates[0]!.replace(/\s.*$/, '') + `~s${refsGeneration}`; const press = await handleInteractionCommands({ - req: { token: 't', session: sessionName, command: 'press', positionals: [pinned], flags: {} }, + + createCommandSurface: testCreateCommandSurface,req: { token: 't', session: sessionName, command: 'press', positionals: [pinned], flags: {} }, sessionName, sessionStore, contextFromFlags, @@ -155,7 +158,8 @@ test('a plain candidate ref from the ambiguity refusal is refused, not silently const fixture = selectorCaptureFixture({ snapshot: () => ambiguousFullCapture() }); const response = await dispatchIsViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: isRequest(sessionName, ['visible', 'label="Deploy"']), sessionName, sessionStore, @@ -169,7 +173,8 @@ test('a plain candidate ref from the ambiguity refusal is refused, not silently // plain (unpinned) ref needs a complete frame and is refused with the // suggested pinned form rather than resolving positionally. const press = await handleInteractionCommands({ - req: { token: 't', session: sessionName, command: 'press', positionals: ['@e2'], flags: {} }, + + createCommandSurface: testCreateCommandSurface,req: { token: 't', session: sessionName, command: 'press', positionals: ['@e2'], flags: {} }, sessionName, sessionStore, contextFromFlags, @@ -218,7 +223,8 @@ test('an ambiguous is on a sparse capture prints no candidates and issues nothin }); const fixture = selectorCaptureFixture({ snapshot: sparseCapture }); const response = await dispatchIsViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: isRequest(sessionName, ['visible', 'label="Deploy"']), sessionName, sessionStore, @@ -288,7 +294,8 @@ test('an ambiguous press issues its printed candidates from its own capture', as }); const response = await handleInteractionCommands({ - req: { + + createCommandSurface: testCreateCommandSurface,req: { token: 't', session: sessionName, command: 'press', diff --git a/src/daemon/__tests__/selector-runtime.test.ts b/src/daemon/__tests__/selector-runtime.test.ts index f35d162094..0a20dfb555 100644 --- a/src/daemon/__tests__/selector-runtime.test.ts +++ b/src/daemon/__tests__/selector-runtime.test.ts @@ -19,6 +19,7 @@ import { createUnavailableRuntimeFactsForTest } from '../../__tests__/test-utils import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import { makeSession as makeStoredSession } from '../../__tests__/test-utils/session-factories.ts'; import { dispatchGetViaRuntime } from '../selector-runtime.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; const { mockRunAppleRunnerCommand } = vi.hoisted(() => ({ mockRunAppleRunnerCommand: vi.fn(), @@ -102,7 +103,8 @@ test.each(['rebuild', 'retire'] as const)( [Symbol.asyncDispose]: async () => {}, }; const running = dispatchGetViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/__tests__/snapshot-command-runtime.test.ts b/src/daemon/__tests__/snapshot-command-runtime.test.ts index 08f9de99b1..b2eca36d16 100644 --- a/src/daemon/__tests__/snapshot-command-runtime.test.ts +++ b/src/daemon/__tests__/snapshot-command-runtime.test.ts @@ -11,6 +11,7 @@ import { dispatchSnapshotDiffViaRuntime } from '../snapshot-diff-runtime.ts'; import { dispatchSnapshotViaRuntime } from '../snapshot-runtime.ts'; import { legacyDispatchCapture } from './legacy-snapshot-capture-fixture.ts'; import { snapshotRuntimeFixture } from './snapshot-runtime-fixture.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; /** * The capture double the snapshot runtime fixture's bound operation delegates to. Cancellation is @@ -73,12 +74,14 @@ for (const command of ['snapshot', 'diff snapshot'] as const) { const running = command === 'snapshot' ? dispatchSnapshotViaRuntime({ - ...input, + + createCommandSurface: testCreateCommandSurface,...input, ...snapshotRuntimeFixture(requestId), platformServices: daemonPlatformServicesFixture(), }) : dispatchSnapshotDiffViaRuntime({ - ...input, + + createCommandSurface: testCreateCommandSurface,...input, ...snapshotRuntimeFixture(requestId), platformServices: daemonPlatformServicesFixture(), }); @@ -114,7 +117,8 @@ for (const change of ['unchanged', 'rebuild', 'replace'] as const) { }; }); const running = dispatchSnapshotViaRuntime({ - req: { command: 'snapshot', positionals: [], token: 't', session: address }, + + createCommandSurface: testCreateCommandSurface,req: { command: 'snapshot', positionals: [], token: 't', session: address }, sessionName: address, logPath: '/dev/null', sessionStore, @@ -169,7 +173,8 @@ test('a composed snapshot refuses its supplied retired lifetime before facts or captureMock.mockResolvedValue({ nodes: [], truncated: false, backend: 'uiautomator' }); await expect( dispatchSnapshotViaRuntime({ - req: { command: 'snapshot', positionals: [], token: 't', session: 'default' }, + + createCommandSurface: testCreateCommandSurface,req: { command: 'snapshot', positionals: [], token: 't', session: 'default' }, sessionName: address, sessionRef: ref, logPath: '/dev/null', diff --git a/src/daemon/__tests__/snapshot-custom-actions-platform-guard.test.ts b/src/daemon/__tests__/snapshot-custom-actions-platform-guard.test.ts index bb5b8e9767..23559999e8 100644 --- a/src/daemon/__tests__/snapshot-custom-actions-platform-guard.test.ts +++ b/src/daemon/__tests__/snapshot-custom-actions-platform-guard.test.ts @@ -22,6 +22,7 @@ import { } from '@agent-device/contracts/platform-runtime-operations'; import { snapshotRuntimeFixture } from './snapshot-runtime-fixture.ts'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; function snapshotDeviceRuntimeGateway(): DeviceRuntimeGateway { const runtime = snapshotRuntimeFixture(); @@ -50,6 +51,7 @@ function handlerForDevice(device: DeviceInfo) { appBundleId: 'com.example.app', } as SessionState); return createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/__tests__/snapshot-diff-runtime.test.ts b/src/daemon/__tests__/snapshot-diff-runtime.test.ts index bf6e829991..7e58bf7a04 100644 --- a/src/daemon/__tests__/snapshot-diff-runtime.test.ts +++ b/src/daemon/__tests__/snapshot-diff-runtime.test.ts @@ -19,6 +19,7 @@ import type { BindDeviceRuntime, InspectDeviceRuntimeFacts } from '../request-ru import { dispatchSnapshotDiffViaRuntime } from '../snapshot-diff-runtime.ts'; import { expectRefusesUnavailableExactOwnerFact } from './runtime-binding-conformance.ts'; import { unavailableDeviceRuntimeGateway } from './test-device-runtime-gateway.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; const available = Object.freeze({ available: true } as const); const unavailable = Object.freeze({ @@ -127,7 +128,8 @@ test.each([ }); const response = await dispatchSnapshotDiffViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { command: 'diff', positionals: ['snapshot'], @@ -187,7 +189,8 @@ test('preserves initialized, unchanged, and changed diff results through one bou const run = async () => await dispatchSnapshotDiffViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { command: 'diff', positionals: ['snapshot'], @@ -236,7 +239,8 @@ test('a sparse internal diff capture returns no screenshot fallback artifact', a sessionStore.publish('diff-runtime', { ...harness.session, appBundleId: 'com.example.app' }); const response = await dispatchSnapshotDiffViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { command: 'diff', positionals: ['snapshot'], token: 't', session: 'diff-runtime' }, sessionName: 'diff-runtime', logPath: '/tmp/diff-runtime.log', diff --git a/src/daemon/__tests__/snapshot-quality-latch.test.ts b/src/daemon/__tests__/snapshot-quality-latch.test.ts index 5a5ba0616b..8eca2c149d 100644 --- a/src/daemon/__tests__/snapshot-quality-latch.test.ts +++ b/src/daemon/__tests__/snapshot-quality-latch.test.ts @@ -17,6 +17,7 @@ import type { SessionState } from '../session-state.ts'; import { legacyDispatchCapture } from './legacy-snapshot-capture-fixture.ts'; import { snapshotRuntimeFixture } from './snapshot-runtime-fixture.ts'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; vi.mock('../snapshot-interactor-capture.ts', async () => { const fixture = await import('./legacy-snapshot-capture-fixture.ts'); @@ -214,6 +215,7 @@ function seedCapture(verdict: SnapshotQualityVerdict, label = 'Continue') { async function dispatchPublicSnapshot(input: ReturnType) { return await dispatchSnapshotViaRuntime({ + createCommandSurface: testCreateCommandSurface, req: { command: 'snapshot', positionals: [], token: 't', session: input.sessionName }, sessionName: input.sessionName, logPath: input.logPath, @@ -241,6 +243,7 @@ test('an internally armed penalty warns once on the first public deferred snapsh // resolution) rendered nothing; without the latch the deferred suppression // would hide the warning from the user entirely. const internal = await dispatchSnapshotViaRuntime({ + createCommandSurface: testCreateCommandSurface, req: { command: 'snapshot', positionals: [], @@ -312,6 +315,7 @@ test('an empty ref-scoped diff latches on the captured verdict, not the retained }); const diff = await dispatchSnapshotDiffViaRuntime({ + createCommandSurface: testCreateCommandSurface, req: { command: 'diff', positionals: [], diff --git a/src/daemon/__tests__/snapshot-runtime-binding.test.ts b/src/daemon/__tests__/snapshot-runtime-binding.test.ts index bd79845f69..7a59458279 100644 --- a/src/daemon/__tests__/snapshot-runtime-binding.test.ts +++ b/src/daemon/__tests__/snapshot-runtime-binding.test.ts @@ -13,6 +13,7 @@ import { import type { DaemonRequest } from '../daemon-request.ts'; import { snapshotRuntimeFixture } from './snapshot-runtime-fixture.ts'; import { ensureDeviceReady } from '../device/device-ready.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; vi.mock('../device/device-ready.ts', () => ({ ensureDeviceReady: vi.fn(async () => {}) })); @@ -43,7 +44,8 @@ test('the owning interface binds exactly the session device the facts were admit const resolved = await resolveBoundSnapshotCaptureRuntime( { - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req, sessionName: 'bind-test', logPath: '/tmp/bind-test.log', diff --git a/src/daemon/__tests__/snapshot-runtime-disclosure.test.ts b/src/daemon/__tests__/snapshot-runtime-disclosure.test.ts index 4ce0805023..9931f60ade 100644 --- a/src/daemon/__tests__/snapshot-runtime-disclosure.test.ts +++ b/src/daemon/__tests__/snapshot-runtime-disclosure.test.ts @@ -11,6 +11,7 @@ import { SessionStore } from '../session-store.ts'; import { handleSnapshotCommands } from '../handlers/snapshot.ts'; import { legacyDispatchCapture } from './legacy-snapshot-capture-fixture.ts'; import { snapshotRuntimeFixture } from './snapshot-runtime-fixture.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; vi.mock('../snapshot-interactor-capture.ts', async () => { const fixture = await import('./legacy-snapshot-capture-fixture.ts'); @@ -168,6 +169,7 @@ async function dispatchSnapshot( flags: Record = {}, ) { const response = await handleSnapshotCommands({ + createCommandSurface: testCreateCommandSurface, req: { command: 'snapshot', positionals: [], diff --git a/src/daemon/__tests__/sparse-fallback-screenshot.test.ts b/src/daemon/__tests__/sparse-fallback-screenshot.test.ts index fdc0aa4373..0e768d238d 100644 --- a/src/daemon/__tests__/sparse-fallback-screenshot.test.ts +++ b/src/daemon/__tests__/sparse-fallback-screenshot.test.ts @@ -14,6 +14,7 @@ import { type ScreenshotRuntimeFixture, type ScreenshotRuntimeFixtureOptions, } from './screenshot-runtime-fixture.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; const SPARSE: SnapshotQualityVerdict = { state: 'sparse', @@ -57,7 +58,8 @@ async function dispatch( internalObservation = false, ) { const response = await dispatchSnapshotViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { command: 'snapshot', positionals: [], diff --git a/src/daemon/__tests__/system-button-runtime.test.ts b/src/daemon/__tests__/system-button-runtime.test.ts index 18bc3c460f..acd7dbd767 100644 --- a/src/daemon/__tests__/system-button-runtime.test.ts +++ b/src/daemon/__tests__/system-button-runtime.test.ts @@ -33,6 +33,7 @@ import { systemButtonCommands, } from './runtime-binding-conformance.ts'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; const macOsDevice = { id: 'macos-host', @@ -174,6 +175,7 @@ test('request router joins home admission to execution, recording, and ref inval activateCompleteRefFrame(session); sessionStore.publish(session.name, session); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: '/tmp/daemon.log', token: 't', sessionStore, diff --git a/src/daemon/__tests__/tv-remote-runtime.test.ts b/src/daemon/__tests__/tv-remote-runtime.test.ts index 4633b9d168..7341deffcd 100644 --- a/src/daemon/__tests__/tv-remote-runtime.test.ts +++ b/src/daemon/__tests__/tv-remote-runtime.test.ts @@ -24,6 +24,7 @@ import type { GenericPlatformExecutionParams } from '../generic-leaf-execution.t import { resolveBoundTvRemoteRuntime } from '../tv-remote-runtime.ts'; import { expectRefusesUnavailableExactOwnerFact } from './runtime-binding-conformance.ts'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; const vegaVvd = { id: 'vega-vvd', @@ -221,6 +222,7 @@ test('request router joins tv-remote admission to execution and ref invalidation activateCompleteRefFrame(session); sessionStore.publish(session.name, session); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: '/tmp/daemon.log', token: 't', sessionStore, diff --git a/src/daemon/__tests__/viewport-runtime.test.ts b/src/daemon/__tests__/viewport-runtime.test.ts index 9d34baaab9..64c05f42ce 100644 --- a/src/daemon/__tests__/viewport-runtime.test.ts +++ b/src/daemon/__tests__/viewport-runtime.test.ts @@ -24,6 +24,7 @@ import type { GenericPlatformExecutionParams } from '../generic-leaf-execution.t import { resolveBoundViewportRuntime } from '../viewport-runtime.ts'; import { expectRefusesUnavailableExactOwnerFact } from './runtime-binding-conformance.ts'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; const webDevice = { id: 'web', @@ -170,6 +171,7 @@ test('request router joins viewport admission to execution, recording, and ref i activateCompleteRefFrame(session); sessionStore.publish(session.name, session); const handler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: '/tmp/daemon.log', token: 't', sessionStore, diff --git a/src/daemon/__tests__/wait-runtime.test.ts b/src/daemon/__tests__/wait-runtime.test.ts index d4983686eb..530dcc835f 100644 --- a/src/daemon/__tests__/wait-runtime.test.ts +++ b/src/daemon/__tests__/wait-runtime.test.ts @@ -29,6 +29,7 @@ import { handleSnapshotCommands } from '../handlers/snapshot.ts'; import { resolveBoundSelectorCapture } from '../selector-capture-binding.ts'; import { dispatchGetViaRuntime } from '../selector-runtime.ts'; import type { DaemonRequest } from '../daemon-request.ts'; +import { testCreateCommandSurface } from './command-surface-fixture.ts'; const webDevice = { id: 'web', @@ -159,7 +160,8 @@ test.each(['rebuild', 'retire'] as const)( const address = 'cwd:sleep-wait:default'; const ref = sessionStore.publish(address, makeSession('default', { device: harness.device })); const running = handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: waitRequest(['100']), sessionName: address, logPath: '/tmp/daemon.log', @@ -214,7 +216,8 @@ async function runWait( } sessionStore.publish(session.name, session); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: waitRequest(positionals, flags), sessionName: session.name, logPath: '/tmp/daemon.log', @@ -469,7 +472,8 @@ test('a read after a natively satisfied text wait captures instead of reusing th expect(harness.captureSnapshot).toHaveBeenCalledOnce(); const read = await dispatchGetViaRuntime({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { command: 'get', positionals: ['text', 'label="cold.start"'], diff --git a/src/daemon/generic-runtime-execution.ts b/src/daemon/generic-runtime-execution.ts index 97f1799a4e..8539e9b2ae 100644 --- a/src/daemon/generic-runtime-execution.ts +++ b/src/daemon/generic-runtime-execution.ts @@ -2,6 +2,7 @@ import type { ResolvedGenericExecution } from './generic-leaf-execution.ts'; import { resolveBoundFocusRuntime } from './focus-runtime.ts'; import { resolveBoundScrollRuntime } from './scroll-runtime.ts'; import type { ScreenshotRuntimeBindings } from './screenshot-runtime-binding.ts'; +import type { CreateDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; import type { DaemonCommandContext } from './context.ts'; import type { DaemonRequest } from './daemon-request.ts'; import type { SessionState } from './session-state.ts'; @@ -29,6 +30,7 @@ export async function resolveGenericRuntimeExecution( req: DaemonRequest; session: SessionState; context: DaemonCommandContext; + createCommandSurface: CreateDaemonCommandSurface; }> & ScreenshotRuntimeBindings, ): Promise { diff --git a/src/daemon/generic-settle.ts b/src/daemon/generic-settle.ts index 6be5c1b48a..1ab5b94bc1 100644 --- a/src/daemon/generic-settle.ts +++ b/src/daemon/generic-settle.ts @@ -1,4 +1,5 @@ import type { CommandFlags } from '@agent-device/contracts/command'; +import type { CreateDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; import type { SettleObservation, SettleParams, @@ -56,6 +57,7 @@ export type GenericSettleObserver = () => Promise export type GenericSettlePlan = { response: DaemonResponse } | { observe?: GenericSettleObserver }; type GenericSettleContext = { + createCommandSurface: CreateDaemonCommandSurface; sessionRef: SessionRef | undefined; req: DaemonRequest; session: SessionState; @@ -123,6 +125,7 @@ function createGenericSettleRuntime( ): ReturnType | undefined { try { return createInteractionRuntime({ + createCommandSurface: context.createCommandSurface, req: context.req, sessionName: context.sessionName, sessionRef: context.sessionRef, diff --git a/src/daemon/handlers/__tests__/react-native.test.ts b/src/daemon/handlers/__tests__/react-native.test.ts index 7d5b49252a..d559c644c9 100644 --- a/src/daemon/handlers/__tests__/react-native.test.ts +++ b/src/daemon/handlers/__tests__/react-native.test.ts @@ -11,6 +11,7 @@ import { resetGetRuntimeFixture, } from '../../__tests__/interaction-get-runtime-fixture.ts'; import { refFrameState } from '../../ref-frame.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; vi.mock('../../snapshot-capture.ts', () => ({ captureSnapshot: vi.fn(), @@ -63,6 +64,7 @@ test('react-native dismiss-overlay taps collapsed warning close affordance inste }); const response = await handleReactNativeCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -121,6 +123,7 @@ test('react-native dismiss-overlay prefers non-trailing collapsed warning close }); const response = await handleReactNativeCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -189,6 +192,7 @@ test('react-native dismiss-overlay does not confuse app dismiss buttons with ove }); const response = await handleReactNativeCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -239,6 +243,7 @@ test('react-native dismiss-overlay rejects unsafe collapsed warning coordinate f }); const response = await handleReactNativeCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -301,6 +306,7 @@ test('react-native dismiss-overlay dismisses RedBox error overlays instead of mi }); const response = await handleReactNativeCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -367,6 +373,7 @@ test('react-native dismiss-overlay reports unverified dismiss when RedBox contro .mockResolvedValueOnce(fullRedBoxSnapshot); const response = await handleReactNativeCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -419,6 +426,7 @@ test('react-native dismiss-overlay uses Dismiss when RedBox Minimize is absent', }); const response = await handleReactNativeCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -470,6 +478,7 @@ test('react-native dismiss-overlay accepts RedBox control labels with keyboard s }); const response = await handleReactNativeCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -529,6 +538,7 @@ test('react-native dismiss-overlay prefers concrete RedBox buttons over labeled }); const response = await handleReactNativeCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -592,6 +602,7 @@ test('react-native dismiss-overlay reports verified success after a clean post-d }); const response = await handleReactNativeCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -656,6 +667,7 @@ test('react-native dismiss-overlay reports sparse verdict instead of no overlay }); const response = await handleReactNativeCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -728,6 +740,7 @@ test('react-native dismiss-overlay reports unverified dismiss when post-dismiss }); const response = await handleReactNativeCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -778,6 +791,7 @@ test('react-native dismiss-overlay reports still-visible overlays with recovery mockCaptureSnapshot.mockResolvedValueOnce(overlaySnapshot).mockResolvedValueOnce(overlaySnapshot); const response = await handleReactNativeCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -821,6 +835,7 @@ test('react-native dismiss-overlay ignores app copy that only mentions RN overla }); const response = await handleReactNativeCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, diff --git a/src/daemon/handlers/__tests__/session-appstate-input.test.ts b/src/daemon/handlers/__tests__/session-appstate-input.test.ts index 6d3b65af03..4f200b8066 100644 --- a/src/daemon/handlers/__tests__/session-appstate-input.test.ts +++ b/src/daemon/handlers/__tests__/session-appstate-input.test.ts @@ -29,6 +29,7 @@ import { type RuntimeFacts, } from '@agent-device/contracts/platform-runtime'; import type { BindDeviceRuntime } from '../../request-runtime-binding.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; test('appstate on iOS requires active session on selected device', async () => { const sessionStore = makeSessionStore(); @@ -55,7 +56,8 @@ test('appstate on iOS requires active session on selected device', async () => { mockDispatch.mockRejectedValue(new Error('snapshot dispatch should not run')); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -102,7 +104,8 @@ test('appstate returns session appName when bundle id is unavailable', async () mockDispatch.mockRejectedValue(new Error('snapshot dispatch should not run')); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -152,7 +155,8 @@ test('appstate fails when iOS session has no tracked app', async () => { mockResolveTargetDevice.mockResolvedValue(selectedDevice); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -186,7 +190,8 @@ test('appstate without session on iOS selector returns SESSION_NOT_FOUND', async mockResolveTargetDevice.mockResolvedValue(selectedDevice); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -210,7 +215,8 @@ test('appstate without session on iOS selector returns SESSION_NOT_FOUND', async test('appstate with explicit missing session returns SESSION_NOT_FOUND', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'sim', @@ -236,7 +242,8 @@ test('appstate with explicit missing session returns SESSION_NOT_FOUND', async ( test('clipboard requires an active session or explicit device selector', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -277,7 +284,8 @@ test('clipboard rejects unsupported iOS physical devices', async () => { mockDispatch.mockRejectedValue(new Error('dispatch should not run for unsupported targets')); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -363,7 +371,8 @@ test('appstate on iOS reads the session app state from the runner when its owner }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: iosSessionRequest(sessionName), sessionName, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), @@ -395,7 +404,8 @@ test('appstate on iOS keeps the session answer, with no state, when no runner is const runtime = appleAppStateRuntime(async () => ({})); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: iosSessionRequest(sessionName), sessionName, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), @@ -425,7 +435,8 @@ test('appstate on iOS keeps the session answer, with no state, when the runner c }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: iosSessionRequest(sessionName), sessionName, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), diff --git a/src/daemon/handlers/__tests__/session-boot-shutdown.test.ts b/src/daemon/handlers/__tests__/session-boot-shutdown.test.ts index b938fc53ce..2dbc6bdda5 100644 --- a/src/daemon/handlers/__tests__/session-boot-shutdown.test.ts +++ b/src/daemon/handlers/__tests__/session-boot-shutdown.test.ts @@ -19,11 +19,13 @@ import { mockShutdownTargetRuntime, } from './session-command-harness.ts'; import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; test('boot requires session or explicit selector', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -66,7 +68,8 @@ test('boot prefers explicit device selector over active session device', async ( mockResolveTargetDevice.mockResolvedValue(selectedDevice); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -111,7 +114,8 @@ test('boot --timeout forwards a startup deadline to bootTarget (#3004)', async ( const beforeMs = Date.now(); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -145,7 +149,8 @@ test('boot without --timeout leaves the startup deadline unset', async () => { mockResolveTargetDevice.mockResolvedValue(selectedDevice); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -181,7 +186,8 @@ test('boot --headless admits a stopped Android emulator through facts and binds booted: true, }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -226,7 +232,8 @@ test('boot rejects the macOS host boot cell after one facts inspection and befor mockResolveTargetDevice.mockResolvedValue(device); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -263,7 +270,8 @@ test('boot admits a stopped Android emulator through normal readiness', async () booted: true, }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -301,7 +309,8 @@ test('boot forwards Android serial admission policy to readiness', async () => { }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -336,7 +345,8 @@ test('boot --headless requires avd selector when device cannot be resolved', asy const sessionStore = makeSessionStore(); mockResolveTargetDevice.mockRejectedValue(new AppError('DEVICE_NOT_FOUND', 'No device found')); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -372,7 +382,8 @@ test('boot --headless rejects non-Android selectors', async () => { booted: false, }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -406,7 +417,8 @@ test('boot keeps --target validation before facts inspection', async () => { booted: false, }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -443,7 +455,8 @@ test('shutdown turns off selected iOS simulator', async () => { mockResolveTargetDevice.mockResolvedValue(selectedDevice); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -490,7 +503,8 @@ test('shutdown rejects active session device and points to close --shutdown', as mockResolveTargetDevice.mockResolvedValue(selectedDevice); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -530,7 +544,8 @@ test('shutdown turns off selected Android emulator', async () => { }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -578,7 +593,8 @@ test('shutdown rejects unsupported physical devices', async () => { }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -622,7 +638,8 @@ test('shutdown returns an error response when selected target shutdown fails', a }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/handlers/__tests__/session-command-replay.test.ts b/src/daemon/handlers/__tests__/session-command-replay.test.ts index 8532d84fef..80ed07e04a 100644 --- a/src/daemon/handlers/__tests__/session-command-replay.test.ts +++ b/src/daemon/handlers/__tests__/session-command-replay.test.ts @@ -8,6 +8,7 @@ import type { DaemonRequest } from '../../daemon-request.ts'; import { replayScriptSourceBundleFor } from '../../../__tests__/test-utils/replay-script-source.ts'; import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; test('replay parses open --relaunch flag and replays open with relaunch semantics', async () => { const sessionStore = makeSessionStore(); @@ -17,7 +18,8 @@ test('replay parses open --relaunch flag and replays open with relaunch semantic const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -56,7 +58,8 @@ test('replay parses runtime set flags and replays runtime command', async () => const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -96,7 +99,8 @@ test('replay parses inline open runtime flags and replays open with runtime payl const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -141,7 +145,8 @@ test('replay delivers scroll --until and wait --raw flags to the dispatched step const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -177,7 +182,8 @@ test('replay inherits parent device selectors for each invoked step', async () = const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -217,7 +223,8 @@ test('replay inherits the parent web platform selector for each invoked step', a const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -261,7 +268,8 @@ test('test --platform web reports no matching scripts, typed or untyped, because fs.writeFileSync(path.join(root, '02-android.ad'), 'context platform=android\nopen "Demo"\n'); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/handlers/__tests__/session-device-claims.test.ts b/src/daemon/handlers/__tests__/session-device-claims.test.ts index 2434d93318..babe863038 100644 --- a/src/daemon/handlers/__tests__/session-device-claims.test.ts +++ b/src/daemon/handlers/__tests__/session-device-claims.test.ts @@ -4,6 +4,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { afterEach, test, vi } from 'vitest'; import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; vi.mock('@agent-device/device-selection/dispatch-resolve', async (importOriginal) => { const actual = @@ -146,7 +147,8 @@ test('failed local open before device setup rolls its device claim back', async await assert.rejects( async () => handleOpenCommand({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', @@ -174,7 +176,8 @@ test('failed local open after dispatch retains its device claim for recovery', a await assert.rejects( async () => handleOpenCommand({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', @@ -205,7 +208,8 @@ test('failed local runtime-hint setup retains its device claim before open dispa await assert.rejects( async () => handleOpenCommand({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', @@ -232,7 +236,8 @@ test('failed local open response rolls its device claim back', async () => { mockResolveTargetDevice.mockResolvedValue(android); const response = await handleOpenCommand({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', @@ -259,7 +264,8 @@ test('cancellation after local device setup retains the device claim for recover try { const response = await handleOpenCommand({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', @@ -291,7 +297,8 @@ test('a canceled attempt lets the next attempt of the same suite open the device markRequestCanceled(requestId); try { const timedOut = await handleOpenCommand({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', @@ -311,7 +318,8 @@ test('a canceled attempt lets the next attempt of the same suite open the device assert.equal(store.get('suite:1-gesture-pan-duration:attempt-1'), undefined); const retry = await handleOpenCommand({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', @@ -340,7 +348,8 @@ test('provider-owned open creates no host-local device claim from its selected o mockDispatch.mockResolvedValue(undefined); const response = await handleOpenCommand({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', @@ -377,7 +386,8 @@ test('a foreign live claim rejects open before platform preparation or mutation' if (acquired.status !== 'acquired') return; const response = await handleOpenCommand({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', @@ -551,7 +561,8 @@ test('an open under a macos-app lease holds only its app, beside other claims on }); const response = await handleOpenCommand({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', diff --git a/src/daemon/handlers/__tests__/session-install-source-ref-frame.test.ts b/src/daemon/handlers/__tests__/session-install-source-ref-frame.test.ts index e4277ea1a2..c1912f452b 100644 --- a/src/daemon/handlers/__tests__/session-install-source-ref-frame.test.ts +++ b/src/daemon/handlers/__tests__/session-install-source-ref-frame.test.ts @@ -12,6 +12,7 @@ import { mockMaterializeAppSourceRuntime, } from './session-command-harness.ts'; import { activateCompleteRefFrame, refFrameState } from '../../ref-frame.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; const invoke = async (): Promise => { throw new Error('install_source ref-frame tests must stay on the runtime route'); @@ -115,7 +116,8 @@ async function dispatchInstallSource(params: { meta: { installSource: { kind: 'path', path: '/tmp/App.apk' } }, }; return await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req, sessionName: 'default', logPath: '/tmp/daemon.log', diff --git a/src/daemon/handlers/__tests__/session-push-ref-frame.test.ts b/src/daemon/handlers/__tests__/session-push-ref-frame.test.ts index 0fea568128..2774a80ffc 100644 --- a/src/daemon/handlers/__tests__/session-push-ref-frame.test.ts +++ b/src/daemon/handlers/__tests__/session-push-ref-frame.test.ts @@ -11,6 +11,7 @@ import { mockPushNotificationRuntime, } from './session-command-harness.ts'; import { activateCompleteRefFrame, refFrameState } from '../../ref-frame.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; const invoke = async (): Promise => { throw new Error('push ref-frame tests must stay on the runtime route'); @@ -90,7 +91,8 @@ async function dispatchPush(params: { flags: {}, }; return await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req, sessionName: 'default', logPath: '/tmp/daemon.log', diff --git a/src/daemon/handlers/__tests__/session-push.test.ts b/src/daemon/handlers/__tests__/session-push.test.ts index a5999b561e..1012dec210 100644 --- a/src/daemon/handlers/__tests__/session-push.test.ts +++ b/src/daemon/handlers/__tests__/session-push.test.ts @@ -17,6 +17,7 @@ import { mockInspectDeviceRuntimeFacts, mockPushNotificationRuntime, } from './session-command-harness.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; const invoke = async (_req: DaemonRequest): Promise => { return { @@ -28,7 +29,8 @@ const invoke = async (_req: DaemonRequest): Promise => { test('push requires active session or explicit device selector', async () => { const sessionStore = makeSessionStore('agent-device-session-push-'); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -66,7 +68,8 @@ test('push validates payload before runtime facts admission', async () => { await expect( handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -107,7 +110,8 @@ test('push runs readiness and notification through one admitted runtime binding' }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -173,7 +177,8 @@ test('push treats an existing brace-prefixed payload as a file before inline JSO try { const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -212,7 +217,8 @@ test('push stops at unavailable facts without binding', async () => { }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -267,7 +273,8 @@ test('push fails closed before binding when a provider owner lacks readiness', a }; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/handlers/__tests__/session-reinstall.test.ts b/src/daemon/handlers/__tests__/session-reinstall.test.ts index 6129cc945a..d5e76d21db 100644 --- a/src/daemon/handlers/__tests__/session-reinstall.test.ts +++ b/src/daemon/handlers/__tests__/session-reinstall.test.ts @@ -11,6 +11,7 @@ import type { import { SessionStore } from '../../session-store.ts'; import type { DaemonRequest, DaemonResponse } from '../../daemon-request.ts'; import type { SessionState } from '../../session-state.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; vi.mock('@agent-device/device-selection/dispatch-resolve', async (importOriginal) => { const actual = @@ -49,7 +50,8 @@ beforeEach(() => { test('reinstall requires active session or explicit device selector', async () => { const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -84,7 +86,8 @@ test('reinstall validates required args before runtime admission', async () => { }), ); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -121,7 +124,8 @@ test('install binds the deployment use exactly once after one facts admission', }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: session.name, @@ -185,7 +189,8 @@ test('install fails closed before binding when its provider owner lacks deployme }; const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: session.name, @@ -230,7 +235,8 @@ test('reinstall cleans an uploaded artifact after the request-scoped operation', vi.mocked(resolveTargetDevice).mockResolvedValue(session.device); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: session.name, @@ -265,7 +271,8 @@ test('HarmonyOS reinstall updates the active session identity from the runtime r mockDeployAppRuntime.mockResolvedValue({ packageName: 'com.example.application' }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: session.name, @@ -315,7 +322,8 @@ test.each(['rebuild', 'retire'] as const)( return { packageName: 'com.example.updated' }; }); const running = handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/handlers/__tests__/session-relaunch-close.test.ts b/src/daemon/handlers/__tests__/session-relaunch-close.test.ts index 9af06a3e6f..8c10e02a79 100644 --- a/src/daemon/handlers/__tests__/session-relaunch-close.test.ts +++ b/src/daemon/handlers/__tests__/session-relaunch-close.test.ts @@ -6,6 +6,7 @@ import { setActiveProviderDeviceRuntimes, } from '../../../provider-device-runtime.ts'; import { installProviderDeviceAdmission } from '../../provider-device-admission.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; // The daemon reads provider ownership through its own typed admission seam; production // installs it from root composition, and these tests compose it the same way. @@ -121,6 +122,7 @@ function createHandler( leaseRegistry: LeaseRegistry = new LeaseRegistry(), ) { return createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/handlers/__tests__/session-relaunch-guards.test.ts b/src/daemon/handlers/__tests__/session-relaunch-guards.test.ts index bbe1249523..cabf31e2a5 100644 --- a/src/daemon/handlers/__tests__/session-relaunch-guards.test.ts +++ b/src/daemon/handlers/__tests__/session-relaunch-guards.test.ts @@ -12,11 +12,13 @@ import { import { handleSessionCommands } from './session-command-harness.ts'; import { makeTestScreenRecordingResource } from '../../../__tests__/test-utils/screen-recording-live-handle.ts'; import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; test('open --relaunch rejects URL targets', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -41,7 +43,8 @@ test('open --relaunch rejects URL targets', async () => { test('open --relaunch fails without app when no session exists', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -66,7 +69,8 @@ test('open --relaunch fails without app when no session exists', async () => { test('open --relaunch rejects Android app binary paths', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -89,7 +93,8 @@ test('open --relaunch rejects Android app binary paths', async () => { test('open --relaunch rejects bare Android app binary filenames', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -123,7 +128,8 @@ test('open --relaunch rejects Android app binary paths for active sessions', asy sessionStore.publish('default', session); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -146,7 +152,8 @@ test('open --relaunch rejects Android app binary paths for active sessions', asy test('open --relaunch rejects Android app binary paths before resolving a new device', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -190,7 +197,8 @@ test('open on in-use device returns DEVICE_IN_USE before readiness checks', asyn }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -245,7 +253,8 @@ test('open on device owned by recording session returns recording recovery hint' }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'test-attempt', diff --git a/src/daemon/handlers/__tests__/session-replay-command.test.ts b/src/daemon/handlers/__tests__/session-replay-command.test.ts index 3a93a9df1d..b3f8f1b0de 100644 --- a/src/daemon/handlers/__tests__/session-replay-command.test.ts +++ b/src/daemon/handlers/__tests__/session-replay-command.test.ts @@ -12,10 +12,12 @@ import { handleReplayTestCommand } from '../session-replay-command.ts'; import { daemonPlatformServicesFixture } from '../../__tests__/platform-services-fixture.ts'; import { makeSessionStore } from './session-test-harness.ts'; import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; test('replay test handler threads the complete video owner into the application command', async () => { const root = mkdtempForTestSync('agent-device-replay-handler-video-'); const params: SessionCommandParams = { + createCommandSurface: testCreateCommandSurface, req: { token: 'token', session: 'default', diff --git a/src/daemon/handlers/__tests__/session-runtime-command.test.ts b/src/daemon/handlers/__tests__/session-runtime-command.test.ts index 623a05d2a0..790a9bcff7 100644 --- a/src/daemon/handlers/__tests__/session-runtime-command.test.ts +++ b/src/daemon/handlers/__tests__/session-runtime-command.test.ts @@ -22,6 +22,7 @@ import { } from '../../__tests__/test-device-runtime-gateway.ts'; import { refFrameState } from '../../ref-frame.ts'; import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; test('runtime set/show/clear manages session-scoped runtime hints before open', async () => { const sessionStore = makeSessionStore(); @@ -31,7 +32,8 @@ test('runtime set/show/clear manages session-scoped runtime hints before open', } satisfies Pick; const setResponse = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { ...baseRequest, command: 'runtime', @@ -51,7 +53,8 @@ test('runtime set/show/clear manages session-scoped runtime hints before open', expect(setResponse?.ok).toBe(true); const showResponse = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { ...baseRequest, command: 'runtime', @@ -76,7 +79,8 @@ test('runtime set/show/clear manages session-scoped runtime hints before open', } const clearResponse = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { ...baseRequest, command: 'runtime', @@ -112,7 +116,8 @@ test('runtime clear removes applied transport hints for the active app', async ( }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -162,7 +167,8 @@ test('runtime clear expires the ref frame at the admitted hint mutation boundary }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -216,7 +222,8 @@ test('runtime clear rejects a false runtime-hints fact before its one implementa }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -267,7 +274,8 @@ test('runtime gesture-viewport admits and binds the exact viewport operation onc gestureRuntimeSpies.gestureViewport.mockClear(); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -336,7 +344,8 @@ for (const phase of ['admission', 'effect'] as const) { } await expect( handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -378,7 +387,8 @@ test('runtime clear expires and uses the latest matching record after admission' return lifecycleRuntimeFacts(target); }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: ref.address, diff --git a/src/daemon/handlers/__tests__/session-selector-dispatch.test.ts b/src/daemon/handlers/__tests__/session-selector-dispatch.test.ts index c0a8265b5a..babc0a9026 100644 --- a/src/daemon/handlers/__tests__/session-selector-dispatch.test.ts +++ b/src/daemon/handlers/__tests__/session-selector-dispatch.test.ts @@ -31,6 +31,7 @@ import { refFrameState } from '../../ref-frame.ts'; import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; import { makeIosSession } from '../../../__tests__/test-utils/session-factories.ts'; import { createUnavailableRuntimeFactsForTest } from '../../../__tests__/test-utils/runtime-operation-facts.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; const available = Object.freeze({ available: true } as const); const keyboardFamilyDenial = Object.freeze({ @@ -76,7 +77,8 @@ test.each(['rebuild', 'retire'] as const)( [Symbol.asyncDispose]: async () => {}, }; const running = handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -177,7 +179,8 @@ test('keyboard dismiss crosses the ADR 0014 seam while keyboard status preserves // dismiss mutates the device → frame expires. const dismissRef = sessionStore.publish(sessionName, makeSession(sessionName, device)); await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -198,7 +201,8 @@ test('keyboard dismiss crosses the ADR 0014 seam while keyboard status preserves sessionStore.retire(dismissRef); sessionStore.publish(sessionName, makeSession(sessionName, device)); await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -242,7 +246,8 @@ test('keyboard dismiss expires the frame before the invocation runs, even when i sessionStore.publish(sessionName, makeSession(sessionName, device)); await expect( handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -264,7 +269,8 @@ test('keyboard dismiss expires the frame before the invocation runs, even when i test('keyboard requires an active session or explicit device selector', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -292,7 +298,8 @@ test('keyboard dismiss requires active iOS session for explicit selectors', asyn const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/handlers/__tests__/session-state-boot-warmup.test.ts b/src/daemon/handlers/__tests__/session-state-boot-warmup.test.ts index 7a9e258fbe..555f19d1e8 100644 --- a/src/daemon/handlers/__tests__/session-state-boot-warmup.test.ts +++ b/src/daemon/handlers/__tests__/session-state-boot-warmup.test.ts @@ -7,6 +7,7 @@ import { } from './session-test-harness.ts'; import { expect, test } from 'vitest'; import { handleSessionCommands } from './session-command-harness.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; test('boot leaves Apple keep-hot policy inside the platform runtime', async () => { mockResolveTargetDevice.mockResolvedValue({ @@ -20,7 +21,8 @@ test('boot leaves Apple keep-hot policy inside the platform runtime', async () = }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/handlers/__tests__/session-trigger.test.ts b/src/daemon/handlers/__tests__/session-trigger.test.ts index 2ab42015b6..d28734d11f 100644 --- a/src/daemon/handlers/__tests__/session-trigger.test.ts +++ b/src/daemon/handlers/__tests__/session-trigger.test.ts @@ -4,6 +4,7 @@ import { test, expect } from 'vitest'; import { handleSessionCommands } from './session-command-harness.ts'; import type { DaemonRequest, DaemonResponse } from '../../daemon-request.ts'; import { makeSessionStore } from '../../../__tests__/test-utils/store-factory.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; const invoke = async (_req: DaemonRequest): Promise => { return { @@ -15,7 +16,8 @@ const invoke = async (_req: DaemonRequest): Promise => { test('trigger-app-event requires active session or explicit device selector', async () => { const sessionStore = makeSessionStore('agent-device-session-trigger-'); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/handlers/__tests__/snapshot-alert.test.ts b/src/daemon/handlers/__tests__/snapshot-alert.test.ts index 8fe7a1fc4e..42466f3aa1 100644 --- a/src/daemon/handlers/__tests__/snapshot-alert.test.ts +++ b/src/daemon/handlers/__tests__/snapshot-alert.test.ts @@ -8,6 +8,7 @@ import { withAppleRunnerProvider } from '@agent-device/platform-apple/runner'; import { runAppleRunnerCommand } from '@agent-device/platform-apple/runner/operations'; import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; import path from 'node:path'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; vi.mock('@agent-device/platform-apple/runner/operations', async (importOriginal) => { const actual = @@ -40,6 +41,7 @@ async function handleSnapshotCommands( ): Promise>> { return await withAppleRunnerProvider(mockRunnerCommand, { deviceId: iosSimulatorDevice.id }, () => handleProductionSnapshotCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, command: 'alert', positionals, flags: {} }, sessionName, logPath: '/tmp/daemon.log', diff --git a/src/daemon/handlers/__tests__/snapshot-handler-freshness.test.ts b/src/daemon/handlers/__tests__/snapshot-handler-freshness.test.ts index 19c34b8a99..7c7621bec8 100644 --- a/src/daemon/handlers/__tests__/snapshot-handler-freshness.test.ts +++ b/src/daemon/handlers/__tests__/snapshot-handler-freshness.test.ts @@ -10,6 +10,7 @@ import { setActiveProviderDeviceRuntimes, } from '../../../provider-device-runtime.ts'; import { installProviderDeviceAdmission } from '../../provider-device-admission.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; // The daemon reads provider ownership through its own typed admission seam; production // installs it from root composition, and these tests compose it the same way. @@ -178,7 +179,8 @@ test('snapshot annotations survive a deferred post-gesture capture into CLI JSON }); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot'), sessionName, logPath: '/tmp/daemon.log', @@ -209,7 +211,8 @@ test('snapshot timeout captures Android screenshot evidence with overlay refs', const sessionStore = makeAndroidTimeoutEvidenceSession(sessionName); mockAndroidTimeoutEvidenceDispatch(); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot'), sessionName, logPath: '/tmp/daemon.log', @@ -239,7 +242,8 @@ test('snapshot warns when recent snapshot node count collapses sharply', async ( ); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot'), sessionName, logPath: '/tmp/daemon.log', @@ -286,7 +290,8 @@ test('snapshot does not warn on expected node drop across presentation modes', a }); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot', { flags: { snapshotInteractiveOnly: true } }), sessionName, logPath: '/tmp/daemon.log', @@ -324,7 +329,8 @@ test('snapshot automatically retries stale Android trees after recent navigation ); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot', { flags: { snapshotInteractiveOnly: true } }), sessionName, logPath: '/tmp/daemon.log', @@ -353,7 +359,8 @@ test('snapshot warns when Android freshness retries still return the previous ro ); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot', { flags: { snapshotInteractiveOnly: true } }), sessionName, logPath: '/tmp/daemon.log', @@ -402,7 +409,8 @@ test('snapshot response includes normalized visibility metadata', async () => { }); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot', { flags: { snapshotInteractiveOnly: true } }), sessionName, logPath: '/tmp/daemon.log', @@ -431,7 +439,8 @@ test('diff snapshot carries stale-tree warnings for recent Android presses', asy ); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'diff', { positionals: ['snapshot'], flags: { snapshotInteractiveOnly: true }, diff --git a/src/daemon/handlers/__tests__/snapshot-handler-wait.test.ts b/src/daemon/handlers/__tests__/snapshot-handler-wait.test.ts index 990bb1ffd2..b77e688a98 100644 --- a/src/daemon/handlers/__tests__/snapshot-handler-wait.test.ts +++ b/src/daemon/handlers/__tests__/snapshot-handler-wait.test.ts @@ -6,6 +6,7 @@ import { setActiveProviderDeviceRuntimes, } from '../../../provider-device-runtime.ts'; import { installProviderDeviceAdmission } from '../../provider-device-admission.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; // The daemon reads provider ownership through its own typed admission seam; production // installs it from root composition, and these tests compose it the same way. @@ -84,6 +85,7 @@ async function runWaitCommand( const sessionStore = makeSessionStore(); sessionStore.publish(sessionName, makeSession(sessionName, device, appAttach(device))); return await handleSnapshotCommands({ + createCommandSurface: testCreateCommandSurface, req: snapshotRequest(sessionName, 'wait', { positionals }), sessionName, logPath: '/tmp/daemon.log', @@ -195,6 +197,7 @@ test('wait text on Android uses freshness-aware capture instead of one-shot snap // The wait budget includes Android's 250 ms freshness retry delay. const response = await handleSnapshotCommands({ + createCommandSurface: testCreateCommandSurface, req: snapshotRequest(sessionName, 'wait', { positionals: ['Create document', '500'] }), sessionName, logPath: '/tmp/daemon.log', @@ -371,6 +374,7 @@ test('wait selector bypasses a fresh matching session snapshot', async () => { }); const response = await handleSnapshotCommands({ + createCommandSurface: testCreateCommandSurface, req: snapshotRequest(sessionName, 'wait', { positionals: ['label="Ready"', '5000'] }), sessionName, logPath: '/tmp/daemon.log', diff --git a/src/daemon/handlers/__tests__/snapshot-handler.test.ts b/src/daemon/handlers/__tests__/snapshot-handler.test.ts index 6b754d4316..70d46026cc 100644 --- a/src/daemon/handlers/__tests__/snapshot-handler.test.ts +++ b/src/daemon/handlers/__tests__/snapshot-handler.test.ts @@ -8,6 +8,7 @@ import { setActiveProviderDeviceRuntimes, } from '../../../provider-device-runtime.ts'; import { installProviderDeviceAdmission } from '../../provider-device-admission.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; // The daemon reads provider ownership through its own typed admission seam; production // installs it from root composition, and these tests compose it the same way. @@ -82,7 +83,8 @@ test('snapshot rejects @ref scope without existing session snapshot', async () = ); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot', { flags: { snapshotScope: '@e1' } }), sessionName, logPath: '/tmp/daemon.log', @@ -104,7 +106,8 @@ test('snapshot on iOS rejects sessions without a tracked app', async () => { const runtime = countingSnapshotRuntime(); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot'), sessionName, logPath: '/tmp/daemon.log', @@ -135,7 +138,8 @@ test('snapshot on iOS without a tracked app carries the detected open command as const platformServices = daemonPlatformServicesFixture(); const response = await handleSnapshotCommands({ - platformServices, + + createCommandSurface: testCreateCommandSurface,platformServices, req: snapshotRequest(sessionName, 'snapshot'), sessionName, logPath: '/tmp/daemon.log', @@ -170,7 +174,8 @@ test('snapshot on provider-backed iOS runs without a tracked app', async () => { const runtime = countingSnapshotRuntime(); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot'), sessionName, logPath: '/tmp/daemon.log', @@ -194,7 +199,8 @@ test('diff on local iOS still requires a tracked app', async () => { sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'diff', { positionals: ['snapshot'] }), sessionName, logPath: '/tmp/daemon.log', @@ -223,7 +229,8 @@ test('snapshot on iOS runs when the session tracks an app', async () => { }); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot'), sessionName, logPath: '/tmp/daemon.log', @@ -259,7 +266,8 @@ test('snapshot re-activates a complete frame; diff preserves it (ADR 0014)', asy }); const snapshotResponse = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot'), sessionName, logPath: '/tmp/daemon.log', @@ -272,7 +280,8 @@ test('snapshot re-activates a complete frame; diff preserves it (ADR 0014)', asy expect(refFrameState(sessionStore.get(sessionName)!)).toBe('active'); const diffResponse = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'diff', { positionals: ['snapshot'] }), sessionName, logPath: '/tmp/daemon.log', @@ -292,7 +301,8 @@ async function runVersionedRefsCommand(params: { command: 'snapshot' | 'diff'; }): Promise | undefined> { const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(params.sessionName, params.command, { positionals: params.command === 'diff' ? ['snapshot'] : [], }), @@ -378,7 +388,8 @@ test('daemon-private snapshot observation advances capture state without publish }); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot', { internal: { observationOnly: true } }), sessionName, logPath: '/tmp/daemon.log', @@ -401,7 +412,8 @@ test('snapshot surfaces filtered-to-zero Android guidance for interactive snapsh legacyDispatchCapture.mockResolvedValue(androidCapture([], { rawNodeCount: 42, maxDepth: 8 })); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot', { flags: { snapshotInteractiveOnly: true, snapshotDepth: 3 }, }), @@ -422,7 +434,8 @@ test('snapshot surfaces filtered-to-zero Android guidance for interactive snapsh test('diff rejects unsupported kind', async () => { const sessionStore = makeSessionStore(); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest('default', 'diff', { positionals: ['unknown'] }), sessionName: 'default', logPath: '/tmp/daemon.log', @@ -440,7 +453,8 @@ test('diff rejects unsupported kind', async () => { test('diff screenshot is not handled daemon-side (client-backed command)', async () => { const sessionStore = makeSessionStore(); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest('default', 'diff', { positionals: ['screenshot'] }), sessionName: 'default', logPath: '/tmp/daemon.log', diff --git a/src/daemon/handlers/__tests__/snapshot-scoped-refs.test.ts b/src/daemon/handlers/__tests__/snapshot-scoped-refs.test.ts index ccd14733c9..d4393186ad 100644 --- a/src/daemon/handlers/__tests__/snapshot-scoped-refs.test.ts +++ b/src/daemon/handlers/__tests__/snapshot-scoped-refs.test.ts @@ -6,6 +6,7 @@ import { makeAndroidSession } from '../../../__tests__/test-utils/session-factor import { makeSessionStore } from '../../../__tests__/test-utils/store-factory.ts'; import { expireRefFrame } from '../../ref-frame.ts'; import { snapshotRuntimeFixture } from '../../__tests__/snapshot-runtime-fixture.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; const ANDROID_SCRIPT_ERROR = 'Unable to load script. Make sure you are running Metro.'; @@ -97,6 +98,7 @@ function requestScopedSnapshot( ) { const runtime = snapshotRuntimeFixture(); return handleProductionSnapshotCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, diff --git a/src/daemon/handlers/__tests__/snapshot-settings-handler.test.ts b/src/daemon/handlers/__tests__/snapshot-settings-handler.test.ts index b9fddc8d5a..b3d47a8bcf 100644 --- a/src/daemon/handlers/__tests__/snapshot-settings-handler.test.ts +++ b/src/daemon/handlers/__tests__/snapshot-settings-handler.test.ts @@ -7,6 +7,7 @@ import { setActiveProviderDeviceRuntimes, } from '../../../provider-device-runtime.ts'; import { installProviderDeviceAdmission } from '../../provider-device-admission.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; // The daemon reads provider ownership through its own typed admission seam; production // installs it from root composition, and these tests compose it the same way. @@ -95,7 +96,8 @@ test('settings rejects unsupported iOS physical devices', async () => { ); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['wifi', 'on'] }), sessionName, logPath: '/tmp/daemon.log', @@ -116,7 +118,8 @@ test('settings clear-app-state dispatches explicit app id without an active app sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['clear-app-state', 'org.reactnavigation.playground'], }), @@ -139,7 +142,8 @@ test('settings clear-app-state rejects missing app id when no app session is bou sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['clear-app-state'] }), sessionName, logPath: '/tmp/daemon.log', @@ -162,7 +166,8 @@ test('settings reset-keychain dispatches without an app id or active app session sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['reset-keychain', 'clear'], }), @@ -184,7 +189,8 @@ test('settings reset-keychain rejects an extra app argument instead of dropping sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['reset-keychain', 'clear', 'com.example.app'], }), @@ -206,7 +212,8 @@ test('settings text-size reads the category the owner holds without mutating any sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['text-size'] }), sessionName, logPath: '/tmp/daemon.log', @@ -230,14 +237,16 @@ test('settings text-size refuses the macOS host on both legs with the same code' sessionStore.publish(sessionName, makeSession(sessionName, macOsDevice)); const read = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['text-size'] }), sessionName, logPath: '/tmp/daemon.log', sessionStore, }); const write = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['text-size', 'large'] }), sessionName, logPath: '/tmp/daemon.log', @@ -263,7 +272,8 @@ test('settings text-size applies a ladder category through the write leg', async sessionStore.publish(sessionName, session); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['text-size', 'accessibility-extra-large'], }), @@ -296,7 +306,8 @@ test('settings text-size refuses an Apple leaf with no content size before it ex for (const positionals of [['text-size'], ['text-size', 'large']]) { const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals }), sessionName, logPath: '/tmp/daemon.log', @@ -322,7 +333,8 @@ test('settings text-size refuses an off-ladder category with the whole ladder', sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['text-size', 'gigantic'] }), sessionName, logPath: '/tmp/daemon.log', @@ -345,7 +357,8 @@ test('settings text-size refuses an off-ladder category with the whole ladder', test('settings usage hint documents canonical faceid states', async () => { const sessionStore = makeSessionStore(); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest('default', 'settings'), sessionName: 'default', logPath: '/tmp/daemon.log', @@ -369,7 +382,8 @@ test('settings on macOS rejects wifi before dispatch with explicit subset guidan sessionStore.publish(sessionName, makeSession(sessionName, macOsDevice)); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['wifi', 'on'] }), sessionName, logPath: '/tmp/daemon.log', @@ -400,7 +414,8 @@ test('settings permission grant targets an explicit app with no session app boun sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['permission', 'grant', 'camera'], input: { app: 'com.example.app' }, @@ -430,7 +445,8 @@ test('settings permission grant keeps the session app when no app is named', asy ); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['permission', 'grant', 'camera'], }), @@ -454,7 +470,8 @@ test('settings permission grant prefers an explicit app over the session app', a ); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['permission', 'grant', 'camera'], input: { app: 'com.other.app' }, @@ -474,7 +491,8 @@ test('settings location on targets an explicit app on an iOS simulator', async ( sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['location', 'on'], input: { app: 'com.example.app' }, @@ -498,7 +516,8 @@ test('settings Android permission grant targets an explicit app with no session sessionStore.publish(sessionName, makeSession(sessionName, androidDevice)); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['permission', 'grant', 'camera'], input: { app: 'com.example.app' }, @@ -524,7 +543,8 @@ test('settings Android location on refuses a named app before reaching the owner sessionStore.publish(sessionName, makeSession(sessionName, androidDevice)); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['location', 'on'], input: { app: 'com.example.app' }, @@ -557,7 +577,8 @@ test('settings iOS location 1 targets an explicit app over the session app', asy ); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['location', '1'], input: { app: 'com.example.app' }, @@ -581,7 +602,8 @@ test('settings Android location true refuses a named app before reaching the own sessionStore.publish(sessionName, makeSession(sessionName, androidDevice)); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['location', 'true'], input: { app: 'com.example.app' }, @@ -605,7 +627,8 @@ test('settings Android location 0 refuses a named app before reaching the owner' sessionStore.publish(sessionName, makeSession(sessionName, androidDevice)); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['location', '0'], input: { app: 'com.example.app' }, @@ -631,7 +654,8 @@ test('settings iOS location set refuses a named app before reaching the owner', sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['location', 'set', '37.7', '-122.4'], input: { app: 'com.example.app' }, @@ -658,7 +682,8 @@ test('settings macOS permission grant refuses a named app before reaching the ow sessionStore.publish(sessionName, makeSession(sessionName, macOsDevice)); const response = await handleSnapshotCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['permission', 'grant', 'screen-recording'], input: { app: 'com.example.app' }, diff --git a/src/daemon/handlers/__tests__/wait-landmark-recording.test.ts b/src/daemon/handlers/__tests__/wait-landmark-recording.test.ts index 5b8124c022..fe7092fb8c 100644 --- a/src/daemon/handlers/__tests__/wait-landmark-recording.test.ts +++ b/src/daemon/handlers/__tests__/wait-landmark-recording.test.ts @@ -23,6 +23,7 @@ import { makeAndroidSession, authoringPublication, } from '../../../__tests__/test-utils/session-factories.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; vi.mock('@agent-device/device-selection/dispatch-resolve', async (importOriginal) => { const actual = @@ -91,6 +92,7 @@ async function runWait(options: { recording?: boolean; req?: DaemonRequest } = { ); sessionStore.publish('default', session); const response = await dispatchWaitViaRuntime({ + createCommandSurface: testCreateCommandSurface, req: options.req ?? waitReq(), sessionName: 'default', logPath: '/tmp/test.log', diff --git a/src/daemon/handlers/session-command-input.ts b/src/daemon/handlers/session-command-input.ts index 8f963219fc..f78332afee 100644 --- a/src/daemon/handlers/session-command-input.ts +++ b/src/daemon/handlers/session-command-input.ts @@ -3,6 +3,7 @@ import type { LeaseLifecycleProvider, ProviderAppCatalog } from '@agent-device/c import type { HostDiagnostics } from '@agent-device/contracts/host-diagnostics'; import type { PlatformResourceCleanup } from '../platform-resource-cleanup.ts'; import type { PlatformRequestScope } from '@agent-device/contracts/platform-runtime-host'; +import type { CreateDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; import type { DaemonInvokeFn, DaemonRequest, DaemonResponse } from '../daemon-request.ts'; import type { AppLogAdmissionLedger } from '../app-log-admission-ledger.ts'; import { type AudioProbeAdmissionLedger } from '@agent-device/capture-kit/audio-probe-admission-ledger'; @@ -42,6 +43,7 @@ export type SessionCommandInput = { retainDeviceExecutionLock?: (deviceId: string) => Promise; throwIfCanceled?: () => void; reconcileOrphanedDeviceClaim: DeviceClaimReconciler; + createCommandSurface: CreateDaemonCommandSurface; platformResourceCleanup?: PlatformResourceCleanup; }; diff --git a/src/daemon/handlers/session.ts b/src/daemon/handlers/session.ts index c0428ab17c..98e8340837 100644 --- a/src/daemon/handlers/session.ts +++ b/src/daemon/handlers/session.ts @@ -37,6 +37,7 @@ const handleSessionInventoryCommandGroup: SessionCommandHandler = ( const handleSessionOpenCommandGroup: SessionCommandHandler = (params) => handleSessionOpenCommands({ req: params.req, + createCommandSurface: params.createCommandSurface, sessionName: params.sessionName, logPath: params.logPath, sessionStore: params.sessionStore, @@ -250,6 +251,7 @@ export async function handleSessionCommands( return await handler({ req, + createCommandSurface: params.createCommandSurface, sessionName, logPath, sessionStore, diff --git a/src/daemon/handlers/snapshot-runtime-route.test.ts b/src/daemon/handlers/snapshot-runtime-route.test.ts index c469c28d8d..582927b10e 100644 --- a/src/daemon/handlers/snapshot-runtime-route.test.ts +++ b/src/daemon/handlers/snapshot-runtime-route.test.ts @@ -1,5 +1,6 @@ import { daemonPlatformServicesFixture } from '../__tests__/platform-services-fixture.ts'; import { beforeEach, expect, test, vi } from 'vitest'; +import { testCreateCommandSurface } from '../__tests__/command-surface-fixture.ts'; const { dispatchSnapshotDiffViaRuntime, dispatchSnapshotViaRuntime } = vi.hoisted(() => ({ dispatchSnapshotDiffViaRuntime: vi.fn(), @@ -38,7 +39,8 @@ test('snapshot route forwards the request runtime facts and binding seams exactl const platformServices = daemonPlatformServicesFixture(); await handleSnapshotCommands({ - platformServices, + + createCommandSurface: testCreateCommandSurface,platformServices, req, sessionName: 'snapshot-route', logPath: '/tmp/snapshot-route.log', @@ -49,6 +51,7 @@ test('snapshot route forwards the request runtime facts and binding seams exactl expect(dispatchSnapshotViaRuntime).toHaveBeenCalledOnce(); expect(dispatchSnapshotViaRuntime).toHaveBeenCalledWith({ + createCommandSurface: testCreateCommandSurface, req, sessionName: 'snapshot-route', logPath: '/tmp/snapshot-route.log', @@ -73,7 +76,8 @@ test('diff route forwards the request runtime facts and binding seams exactly on const platformServices = daemonPlatformServicesFixture(); await handleSnapshotCommands({ - platformServices, + + createCommandSurface: testCreateCommandSurface,platformServices, req, sessionName: 'diff-route', logPath: '/tmp/diff-route.log', @@ -84,6 +88,7 @@ test('diff route forwards the request runtime facts and binding seams exactly on expect(dispatchSnapshotDiffViaRuntime).toHaveBeenCalledOnce(); expect(dispatchSnapshotDiffViaRuntime).toHaveBeenCalledWith({ + createCommandSurface: testCreateCommandSurface, req, sessionName: 'diff-route', logPath: '/tmp/diff-route.log', diff --git a/src/daemon/handlers/snapshot.ts b/src/daemon/handlers/snapshot.ts index 37e34f74ac..83d09fffda 100644 --- a/src/daemon/handlers/snapshot.ts +++ b/src/daemon/handlers/snapshot.ts @@ -9,9 +9,11 @@ import { dispatchWaitViaRuntime } from '../wait-runtime.ts'; import { resolveSessionDevice, withSessionlessRunnerCleanup } from '../snapshot-session.ts'; import type { BindDeviceRuntime, InspectDeviceRuntimeFacts } from '../request-runtime-binding.ts'; import type { PlatformResourceCleanup } from '../platform-resource-cleanup.ts'; +import type { CreateDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; import { errorResponse } from '@agent-device/kernel/contracts'; type SnapshotCommandParams = { + createCommandSurface: CreateDaemonCommandSurface; req: DaemonRequest; sessionName: string; logPath: string; @@ -27,6 +29,7 @@ type SnapshotCommandHandler = (params: SnapshotCommandParams) => Promise await dispatchSnapshotViaRuntime({ + createCommandSurface, req, sessionName, logPath, @@ -47,6 +51,7 @@ const SNAPSHOT_COMMAND_HANDLER_IMPLS = { }), diff: async ({ req, + createCommandSurface, sessionName, logPath, sessionStore, @@ -59,6 +64,7 @@ const SNAPSHOT_COMMAND_HANDLER_IMPLS = { return errorResponse('INVALID_ARGS', 'diff currently supports only: diff snapshot'); } return await dispatchSnapshotDiffViaRuntime({ + createCommandSurface, req, sessionName, logPath, @@ -71,6 +77,7 @@ const SNAPSHOT_COMMAND_HANDLER_IMPLS = { }, wait: async ({ req, + createCommandSurface, sessionName, logPath, sessionStore, @@ -80,6 +87,7 @@ const SNAPSHOT_COMMAND_HANDLER_IMPLS = { platformResourceCleanup, }) => await dispatchWaitViaRuntime({ + createCommandSurface, req, sessionName, logPath, diff --git a/src/daemon/interaction/internal/__tests__/find-handler-fixture.ts b/src/daemon/interaction/internal/__tests__/find-handler-fixture.ts index dd9bac4fc9..d5cddc5d4b 100644 --- a/src/daemon/interaction/internal/__tests__/find-handler-fixture.ts +++ b/src/daemon/interaction/internal/__tests__/find-handler-fixture.ts @@ -2,6 +2,7 @@ import type { SessionStore } from '../../../session-store.ts'; import type { DaemonRequest, DaemonResponse } from '../../../daemon-request.ts'; import { handleFindCommands } from '../find.ts'; import { getRuntimeBindings } from '../../../__tests__/interaction-get-runtime-fixture.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; /** * One `handleFindCommands` invocation shape. @@ -19,6 +20,7 @@ export function invokeFindHandler(params: { }) { const { sessionName, sessionStore, positionals, flags } = params; return handleFindCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, command: 'find', positionals, flags: flags ?? {} }, sessionName, logPath: '/tmp/test.log', diff --git a/src/daemon/interaction/internal/__tests__/find-production-route.test.ts b/src/daemon/interaction/internal/__tests__/find-production-route.test.ts index 35adfffcda..8e953ed5b6 100644 --- a/src/daemon/interaction/internal/__tests__/find-production-route.test.ts +++ b/src/daemon/interaction/internal/__tests__/find-production-route.test.ts @@ -4,6 +4,7 @@ import type { CommandFlags } from '@agent-device/contracts/command'; import type { DaemonRequest, DaemonResponse } from '../../../daemon-request.ts'; import { makeSessionStore } from '../../../../__tests__/test-utils/store-factory.ts'; import { makeIosSession as makeSession } from '../../../../__tests__/test-utils/session-factories.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; vi.mock('@agent-device/device-selection/dispatch-resolve', async (importOriginal) => { const actual = (await importOriginal()) as Record; @@ -115,6 +116,7 @@ async function runFindThroughLeaf(options: { // The real leaf, not a stub. return ( (await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req, sessionName, sessionStore, diff --git a/src/daemon/interaction/internal/__tests__/find-target-activation-disclosure.test.ts b/src/daemon/interaction/internal/__tests__/find-target-activation-disclosure.test.ts index 2ed86a118b..d7ab923568 100644 --- a/src/daemon/interaction/internal/__tests__/find-target-activation-disclosure.test.ts +++ b/src/daemon/interaction/internal/__tests__/find-target-activation-disclosure.test.ts @@ -9,6 +9,7 @@ import { getRuntimeBindings } from '../../../__tests__/interaction-get-runtime-f import { handleFindCommands } from '../../index.ts'; import { markDeferredInteractionOutcome } from '../../../deferred-interaction-outcome.ts'; import { formatPostGestureOutcomeWarning } from '@agent-device/capture-kit/post-gesture-stability'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; vi.mock('../../../snapshot-interactor-capture.ts', async () => { const fixture = await import('../../../__tests__/legacy-snapshot-capture-fixture.ts'); @@ -90,6 +91,7 @@ async function findClick(captures: Record[] | CaptureScript, af ); const response = await handleFindCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: 'default', diff --git a/src/daemon/interaction/internal/__tests__/interaction-capture-disclosure.test.ts b/src/daemon/interaction/internal/__tests__/interaction-capture-disclosure.test.ts index 93b7160a79..e7c9d60f20 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-capture-disclosure.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-capture-disclosure.test.ts @@ -13,6 +13,7 @@ import { contextFromFlags, makeSession } from './interaction-touch-fixtures.ts'; import { legacyDispatchCapture } from '../../../__tests__/legacy-snapshot-capture-fixture.ts'; import { markDeferredInteractionOutcome } from '../../../deferred-interaction-outcome.ts'; import { formatPostGestureOutcomeWarning } from '@agent-device/capture-kit/post-gesture-stability'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; vi.mock('../../../snapshot-interactor-capture.ts', async () => { const fixture = await import('../../../__tests__/legacy-snapshot-capture-fixture.ts'); @@ -57,6 +58,7 @@ async function pressSelector(params: { const sessionStore = makeSessionStore(); sessionStore.publish(params.sessionName, makeSession(params.sessionName)); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: params.sessionName, @@ -109,6 +111,7 @@ test('a press that consumes no capture is not disclosed against an older tree', sessionStore.publish(sessionName, session); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -168,6 +171,7 @@ async function pressAfterUnsettledScroll(selector: string) { vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }); let done = false; const pending = handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: 'default', command: 'press', positionals: [selector], flags: {} }, sessionName: 'default', sessionStore, diff --git a/src/daemon/interaction/internal/__tests__/interaction-common.test.ts b/src/daemon/interaction/internal/__tests__/interaction-common.test.ts index 3835266ac0..7c7b894ecd 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-common.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-common.test.ts @@ -15,6 +15,7 @@ import { mockFillPoint, resetGetRuntimeFixture, } from '../../../__tests__/interaction-get-runtime-fixture.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; const contextFromFlags = (_flags: CommandFlags | undefined) => ({}); @@ -161,6 +162,7 @@ test('parameterized fill scrubs concatenated backend values and object keys thro ); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -218,6 +220,7 @@ test('parameterized fill collapses whitespace-only backend echoes through the ha }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, diff --git a/src/daemon/interaction/internal/__tests__/interaction-dispatch-disclosure.test.ts b/src/daemon/interaction/internal/__tests__/interaction-dispatch-disclosure.test.ts index ed38ce5f8b..faf5b4e9b0 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-dispatch-disclosure.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-dispatch-disclosure.test.ts @@ -36,6 +36,7 @@ import { makeSession, makeStaleRefSession, } from './interaction-touch-fixtures.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; // contracts/fixtures/dispatch-disclosure.json, daemon and post-action guard rows: the daemon rows // drive a real `press` through the daemon interaction handler, inside the router's dispatch seam, @@ -95,7 +96,8 @@ async function press({ }; sessionStore.publish(session.name, session); const response = await routeInteraction({ - req: { token: 't', session: session.name, command, positionals, flags }, + + createCommandSurface: testCreateCommandSurface,req: { token: 't', session: session.name, command, positionals, flags }, sessionName: session.name, sessionStore, contextFromFlags, @@ -192,7 +194,8 @@ async function swipeRefusedOnSecondRepetition(): Promise { const session = makeSession('dispatch-disclosure-swipe'); sessionStore.publish(session.name, session); const response = await routeInteraction({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: session.name, @@ -225,7 +228,8 @@ async function gestureDragRefusedBeforeDispatch(): Promise { expireRefFrame(session); sessionStore.publish(session.name, session); const response = await routeInteraction({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: session.name, @@ -283,7 +287,8 @@ async function refRefusedAfterAndroidRecovery(command: 'press' | 'gesture'): Pro openApp, }; const response = await routeInteraction({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: session.name, @@ -339,7 +344,8 @@ async function pressThenForegroundReadRefused(): Promise { }, }; const response = await routeInteraction({ - req: { token: 't', session: session.name, command: 'press', positionals: ['50', '40'] }, + + createCommandSurface: testCreateCommandSurface,req: { token: 't', session: session.name, command: 'press', positionals: ['50', '40'] }, sessionName: session.name, sessionStore, contextFromFlags, @@ -449,7 +455,8 @@ test('a plain Error a backend throws reaches the wire with dispatched unknown', }) as unknown as typeof bindings.bindDevice; await assert.rejects( routeInteraction({ - req: { token: 't', session: session.name, command: 'press', positionals: ['@e1'], flags: {} }, + + createCommandSurface: testCreateCommandSurface,req: { token: 't', session: session.name, command: 'press', positionals: ['@e1'], flags: {} }, sessionName: session.name, sessionStore, contextFromFlags, diff --git a/src/daemon/interaction/internal/__tests__/interaction-gesture-drag.test.ts b/src/daemon/interaction/internal/__tests__/interaction-gesture-drag.test.ts index 2c22b17b8d..3a2e1675f2 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-gesture-drag.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-gesture-drag.test.ts @@ -10,6 +10,7 @@ import { activateCompleteRefFrame, refFrameState } from '../../../ref-frame.ts'; import { handleInteractionCommands } from '../../index.ts'; import { gestureRuntimeBindingsFixture } from './gesture-runtime-bindings.fixtures.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; const contextFromFlags = () => ({}); const captureSnapshotForSession = async ({ @@ -62,7 +63,8 @@ function makeDragSession(sessionName: string) { async function runDrag(sessionStore: ReturnType, sessionName: string) { return await handleInteractionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, diff --git a/src/daemon/interaction/internal/__tests__/interaction-get.test.ts b/src/daemon/interaction/internal/__tests__/interaction-get.test.ts index 9b35dba6fc..0378625dc6 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-get.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-get.test.ts @@ -18,6 +18,7 @@ import { makeVisibleButtonSnapshot, runInteraction, } from './interaction-touch-fixtures.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; // Non-touch interaction routing: the `get` and `is` reads the public handler // owns. Touch commands live in the interaction-touch* test files. @@ -99,6 +100,7 @@ test('get text prefers underlying value for text surfaces and avoids recording g ); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -150,6 +152,7 @@ test('get text uses backend read expansion when the resolved node has a rect', a ); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -196,6 +199,7 @@ test('get text answers from the captured tree when the bound owner advertises no elementReadFixtureState.readTextAtPointAvailable = false; const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -238,6 +242,7 @@ test('an eligible direct iOS selector cannot operate before admission', async () }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -297,6 +302,7 @@ test('get text simple iOS id selector resolves through the bound capture, not a }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -376,6 +382,7 @@ test('get text iOS label selector uses snapshot disambiguation instead of runner }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -434,6 +441,7 @@ test('is visible preserves CLI snapshot flags during runtime snapshot capture', }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -469,6 +477,7 @@ test('is visible reuses fresh cached iOS snapshots with rects', async () => { legacyDispatchCapture.mockRejectedValue(new Error('unexpected fresh snapshot')); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -508,6 +517,7 @@ test('is visible recaptures web snapshots when cached nodes may lack rects', asy ); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -557,6 +567,7 @@ test('a failing is predicate is COMMAND_FAILED, never a zero-exit pass', async ( }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -610,6 +621,7 @@ test('is visible passes for list text that inherits viewport visibility from an }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -657,6 +669,7 @@ test('is visible fails for nodes outside the current viewport', async () => { }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -696,6 +709,7 @@ test('is reports Android permission dialog blocker when app content assertion fa }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, diff --git a/src/daemon/interaction/internal/__tests__/interaction-ios-tap-outcome.test.ts b/src/daemon/interaction/internal/__tests__/interaction-ios-tap-outcome.test.ts index 95827268d6..efb1deeb42 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-ios-tap-outcome.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-ios-tap-outcome.test.ts @@ -34,6 +34,7 @@ import { import { captureSnapshotWithInteractor } from '../../../snapshot-interactor-capture.ts'; import { corroborateIosTapFailure } from '../interaction-ios-tap-outcome.ts'; import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; vi.mock('../../../snapshot-interactor-capture.ts', async () => { const fixture = await import('../../../__tests__/legacy-snapshot-capture-fixture.ts'); @@ -73,6 +74,7 @@ async function runClick( }); } return await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 'test', session: sessionName, @@ -472,6 +474,7 @@ test('corroborates a tap when the request carries no flags and the baseline used // mirrors the raw daemon/JSON-RPC production boundary, which can omit the // key entirely. CLI and batch paths always materialize a flags object. const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 'test', session: sessionName, @@ -730,6 +733,7 @@ test('corroborated runtime taps retain target evidence through save and replay', sessionStore: replayStore, invoke: async (req) => { const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req, sessionName: replaySessionName, sessionStore: replayStore, @@ -763,6 +767,7 @@ test('a tap corroborates against its admitted snapshot after a same-lifetime rec return {}; }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 'test', session: name, command: 'click', positionals: ['104', '222'], flags: {} }, sessionName: name, sessionRef: ref, diff --git a/src/daemon/interaction/internal/__tests__/interaction-settle-private-ax-route.test.ts b/src/daemon/interaction/internal/__tests__/interaction-settle-private-ax-route.test.ts index 97e062f404..439b2da8df 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-settle-private-ax-route.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-settle-private-ax-route.test.ts @@ -8,6 +8,7 @@ import { handleInteractionCommands } from '../../index.ts'; import { getRuntimeBindings } from '../../../__tests__/interaction-get-runtime-fixture.ts'; import { getInteractor } from '../../../../core/interactors.ts'; import { installInteractorResolution } from '../../../interactor-resolution.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; // Root composition hands the daemon its interactor resolution; this test drives the real // settle capture, so it composes the real lookup the way the process root does. @@ -74,6 +75,7 @@ test('daemon press --settle pins private-ax on emitted snapshot runner requests' const response = await withAppleRunnerProvider(mockRunnerCommand, { deviceId: 'sim-1' }, () => handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, diff --git a/src/daemon/interaction/internal/__tests__/interaction-target-evidence.test.ts b/src/daemon/interaction/internal/__tests__/interaction-target-evidence.test.ts index bfd8845d7b..1a1ad0a512 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-target-evidence.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-target-evidence.test.ts @@ -14,6 +14,7 @@ import { import { SessionScriptWriter } from '../../../session-script-writer.ts'; import type { SessionState } from '../../../session-state.ts'; import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; // ADR 0012 decision 3, daemon-routed recording: target-v1 evidence is // computed only while the session is being recorded, lands on the recorded @@ -90,6 +91,7 @@ async function runCommand( flags: CommandFlags = {}, ) { return await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, command, positionals, flags }, sessionName, sessionStore, diff --git a/src/daemon/interaction/internal/__tests__/interaction-touch-android-freshness.test.ts b/src/daemon/interaction/internal/__tests__/interaction-touch-android-freshness.test.ts index 70669107e3..0ce2ba5fb5 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-touch-android-freshness.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-touch-android-freshness.test.ts @@ -8,6 +8,7 @@ import { resetGetRuntimeFixture, } from '../../../__tests__/interaction-get-runtime-fixture.ts'; import { contextFromFlags, makeAndroidSession } from './interaction-touch-fixtures.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; // The Android ref-refresh capture a @ref mutation takes before dispatch: when // it runs, what it may not retarget, and the comparison-safe baseline it hands @@ -105,6 +106,7 @@ test('press @ref refreshes Android snapshot when freshness tracking is active', }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -179,6 +181,7 @@ test('ADR 0014: Android freshness cannot retarget an admitted ref by positional }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -229,6 +232,7 @@ test('press @ref falls back to cached Android ref when freshness refresh fails', mockCaptureSnapshotForSession.mockRejectedValueOnce(new Error('uiautomator timeout')); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -296,6 +300,7 @@ test('coordinate press preserves Android route freshness from last comparable sn sessionStore.publish(sessionName, session); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, diff --git a/src/daemon/interaction/internal/__tests__/interaction-touch-android-readiness.test.ts b/src/daemon/interaction/internal/__tests__/interaction-touch-android-readiness.test.ts index df7be26605..c5a1472053 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-touch-android-readiness.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-touch-android-readiness.test.ts @@ -9,6 +9,7 @@ import { resetGetRuntimeFixture, } from '../../../__tests__/interaction-get-runtime-fixture.ts'; import { contextFromFlags, makeAndroidSession } from './interaction-touch-fixtures.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; // The Android device state a dispatch has to survive: an escape to launcher or // Settings fails the press, a permission prompt warns instead, and @@ -110,6 +111,7 @@ test('press @ref fails when Android tap escapes to launcher', async () => { await expect( handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -158,6 +160,7 @@ test('press @ref fails when Android tap escapes to Settings', async () => { await expect( handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -212,6 +215,7 @@ test.each(ANDROID_PERMISSION_PROMPT_PACKAGES)( }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -271,6 +275,7 @@ test('a ref action aborts with the shared ref_frame_expired rejection after Andr sessionStore.publish(sessionName, session); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, command: 'press', positionals: ['@e1'], flags: {} }, sessionName, sessionStore, diff --git a/src/daemon/interaction/internal/__tests__/interaction-touch-direct-ios-eligibility.test.ts b/src/daemon/interaction/internal/__tests__/interaction-touch-direct-ios-eligibility.test.ts index 53be62df8a..0fe3fe959e 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-touch-direct-ios-eligibility.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-touch-direct-ios-eligibility.test.ts @@ -11,6 +11,7 @@ import { resetGetRuntimeFixture, } from '../../../__tests__/interaction-get-runtime-fixture.ts'; import { contextFromFlags } from './interaction-touch-fixtures.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; // Ordinary selectors stay capture-backed. Only the explicit Maestro // non-hittable fallback may use the direct iOS selector route. @@ -92,6 +93,7 @@ test('ordinary click uses canonical capture even when the owner binds selector t }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -149,6 +151,7 @@ test('fill simple iOS id selector resolves runtime text input evidence before co }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -196,6 +199,7 @@ test('click simple iOS selector forwards Maestro non-hittable coordinate fallbac }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -256,6 +260,7 @@ test('click simple iOS id selector waits for snapshot path after pending gesture }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, diff --git a/src/daemon/interaction/internal/__tests__/interaction-touch-direct-ios.test.ts b/src/daemon/interaction/internal/__tests__/interaction-touch-direct-ios.test.ts index b001eac25d..ec02d9f544 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-touch-direct-ios.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-touch-direct-ios.test.ts @@ -24,6 +24,7 @@ import { } from './interaction-touch-fixtures.ts'; import { refFrameState } from '../../../ref-frame.ts'; import { captureSnapshotWithInteractor } from '../../../snapshot-interactor-capture.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; vi.mock('@agent-device/platform-android/mechanics', async (importOriginal) => { const actual = await importOriginal(); @@ -59,6 +60,7 @@ test.each([ mockTapElementSelector.mockRejectedValue(new AppError(code, message)); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, diff --git a/src/daemon/interaction/internal/__tests__/interaction-touch-fill.test.ts b/src/daemon/interaction/internal/__tests__/interaction-touch-fill.test.ts index 905028089a..dec43e319b 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-touch-fill.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-touch-fill.test.ts @@ -16,6 +16,7 @@ import { makeStaleRefSession, runInteraction, } from './interaction-touch-fixtures.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; // How fill is admitted and projected: its @ref admission and pins, the editable // node it keeps, and the guards that refuse before typing. @@ -100,6 +101,7 @@ test('fill @ref fails fast when the target is off-screen', async () => { const response = await withRunner(() => handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -160,6 +162,7 @@ test('fill @ref fails closed when stored ref bounds are invalid (ADR 0014)', asy ); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -306,6 +309,7 @@ test("ADR 0014 blocker-2: a mutating find's internal fill from an expired frame const findNodes = session.snapshot.nodes; const findNode = findNodes[0]!; const internal = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -360,6 +364,7 @@ test('fill @ref keeps the original editable node when its parent is the hittable mockFillPoint.mockResolvedValue({ filled: true }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, diff --git a/src/daemon/interaction/internal/__tests__/interaction-touch-fixtures.ts b/src/daemon/interaction/internal/__tests__/interaction-touch-fixtures.ts index e29b555739..87fcb9ad59 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-touch-fixtures.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-touch-fixtures.ts @@ -12,6 +12,7 @@ import type { SessionState } from '../../../session-state.ts'; import { handleInteractionCommands } from '../../index.ts'; import { getRuntimeBindings } from '../../../__tests__/interaction-get-runtime-fixture.ts'; import { buildSnapshotState } from '@agent-device/capture-kit/snapshot-state'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; /** * Shared factories for the interaction touch handler tests. Named pure @@ -127,6 +128,7 @@ export async function runInteraction( flags: Record = {}, ) { return await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, command, positionals, flags }, sessionName, sessionStore, @@ -168,6 +170,7 @@ export async function runFindInternalClick( internal: Record, ) { return await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, diff --git a/src/daemon/interaction/internal/__tests__/interaction-touch-payload.test.ts b/src/daemon/interaction/internal/__tests__/interaction-touch-payload.test.ts index 3d424ce2d6..1bcabef0c5 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-touch-payload.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-touch-payload.test.ts @@ -13,6 +13,7 @@ import { makeAndroidSession, makeSession, } from './interaction-touch-fixtures.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; // What a built touch payload carries: the recorded action entry and touch // visualization, the coordinates and reference frame each platform resolves, @@ -97,6 +98,7 @@ test('press coordinates appends touch-visualization events while recording', asy }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -163,6 +165,7 @@ test('press coordinates on iOS recording captures a full snapshot for the touch }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -203,6 +206,7 @@ test('press coordinates on Android recording uses physical screen size when no s mockTapPoint.mockResolvedValue({ x: 300, y: 2300 }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -239,6 +243,7 @@ test('press coordinates on Android recording caches physical screen size across mockTapPoint.mockResolvedValue({ x: 300, y: 2300 }); await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -255,6 +260,7 @@ test('press coordinates on Android recording caches physical screen size across mockTapPoint.mockResolvedValue({ x: 320, y: 2200 }); await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -286,6 +292,7 @@ test('press coordinates without recording skips Android screen-size lookup', asy mockTapPoint.mockResolvedValue({ x: 300, y: 2300 }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -320,6 +327,7 @@ test('press coordinates during recording still dispatches when Android screen-si mockGetAndroidScreenSize.mockRejectedValue(new Error('adb unavailable')); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -384,6 +392,7 @@ test('press @ref preserves native timing in recorded result and touch visualizat }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, diff --git a/src/daemon/interaction/internal/__tests__/interaction-touch-press-admission.test.ts b/src/daemon/interaction/internal/__tests__/interaction-touch-press-admission.test.ts index 9a855d8abb..3f658eab1f 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-touch-press-admission.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-touch-press-admission.test.ts @@ -20,6 +20,7 @@ import { runFindInternalClick, runInteraction, } from './interaction-touch-fixtures.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; // Whether a targeted touch may act, and on what: click-option and positional // validation, and the ADR 0014 @ref rules (staleness, pins, frame expiry, @@ -92,6 +93,7 @@ test('click --button middle on macOS fails with an explicit unsupported-operatio ); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -119,6 +121,7 @@ test('press coordinates does not treat extra trailing args as selector', async ( sessionStore.publish(sessionName, makeSession(sessionName)); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, diff --git a/src/daemon/interaction/internal/__tests__/interaction-touch-press.test.ts b/src/daemon/interaction/internal/__tests__/interaction-touch-press.test.ts index 01ef13c6f2..a34fab1669 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-touch-press.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-touch-press.test.ts @@ -18,6 +18,7 @@ import { readPressPoint, runFindInternalClick, } from './interaction-touch-fixtures.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; // Which node an admitted press/click acts on: hittable-ancestor promotion, the // macOS menubar wrapper, button projection, and a mutating find's pre-resolved @@ -110,6 +111,7 @@ test('click on a macOS menubar wrapper ref promotes to the same-rect menu bar it sessionStore.publish(sessionName, session); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -169,6 +171,7 @@ test('press @ref promotes a non-hittable node to its hittable ancestor before ta mockTapPoint.mockResolvedValue({ pressed: true }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -231,6 +234,7 @@ test('press @ref does not promote to a full-screen hittable ancestor', async () mockTapPoint.mockResolvedValue({ pressed: true }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -290,6 +294,7 @@ test('click --button secondary on @ref dispatches a secondary press on macOS and mockTapPoint.mockResolvedValue({ button: 'secondary' }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -431,6 +436,7 @@ test('a read right after a press captures the post-tap screen instead of reusing .mockResolvedValue(screen('Step 2')); const run = async (command: string, positionals: string[]) => await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, command, positionals, flags: {} }, sessionName, sessionStore, diff --git a/src/daemon/interaction/internal/__tests__/interaction-touch-response.test.ts b/src/daemon/interaction/internal/__tests__/interaction-touch-response.test.ts index dfd13ff020..1751ba8c88 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-touch-response.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-touch-response.test.ts @@ -27,6 +27,7 @@ import { installTestScreenRecording, makeSession, } from './interaction-touch-fixtures.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; // The identity extras the one response site composes: --verify evidence rides // the interactionResultExtra allowlist on every branch, and no branch invents @@ -141,6 +142,7 @@ test.each([false, true])('selector-touch observation after retirement=%s', async } const payloads = await buildTargetedTouchResponsePayloads({ params: { + createCommandSurface: testCreateCommandSurface, req: { token: 't', command: 'press', positionals: ['@e1'], session: 'default' }, sessionName: address, sessionRef: ref, @@ -211,6 +213,7 @@ test.each([ async () => { const payloads = await buildTargetedTouchResponsePayloads({ params: { + createCommandSurface: testCreateCommandSurface, req: { token: 't', command: 'press', @@ -327,6 +330,7 @@ test('press @ref --verify surfaces evidence through the interactionResultExtra a }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -385,6 +389,7 @@ test('press @ref without --verify never includes an evidence field', async () => mockTapPoint.mockResolvedValue({ pressed: true }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -443,6 +448,7 @@ test('fill selector --verify surfaces evidence through the interactionResultExtr }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -512,6 +518,7 @@ test('fill @ref --verify surfaces evidence in the ref response branch', async () }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -558,6 +565,7 @@ test('fill @ref without --verify never includes an evidence field', async () => sessionStore.publish(sessionName, session); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -608,6 +616,7 @@ test('fill @ref preserves fallback coordinates for recording when platform resul mockFillPoint.mockResolvedValue({ filled: true }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -650,6 +659,7 @@ test('an already retired coordinate touch skips its frame probe without a warnin async () => { const payloads = await buildTargetedTouchResponsePayloads({ params: { + createCommandSurface: testCreateCommandSurface, req: { token: 't', command: 'click', positionals: ['1', '2'], session: ref.address }, sessionName: ref.address, sessionRef: ref, diff --git a/src/daemon/interaction/internal/__tests__/interaction-touch-runtime.test.ts b/src/daemon/interaction/internal/__tests__/interaction-touch-runtime.test.ts index fda5a2b5da..99261e45a3 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-touch-runtime.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-touch-runtime.test.ts @@ -9,6 +9,7 @@ import { resetGetRuntimeFixture, } from '../../../__tests__/interaction-get-runtime-fixture.ts'; import { contextFromFlags, makeSession } from './interaction-touch-fixtures.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; // What the shared runtime dispatch does with the resolved target: refuse // unusable frame evidence rather than recapture positionally (ADR 0014), refuse @@ -85,6 +86,7 @@ test('press @ref taps the resolved point once and records the ref', async () => sessionStore.publish(sessionName, session); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -130,6 +132,7 @@ test('press @ref fails closed when the authorized ref has no usable bounds (ADR ); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -187,6 +190,7 @@ test('press @ref fails closed when stored ref bounds are invalid (ADR 0014)', as ); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -240,6 +244,7 @@ test('press @ref fails fast when the target is off-screen', async () => { const response = await withRunner(() => handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -315,6 +320,7 @@ test('press @ref with a trailing label recovers within the authorized frame (no mockTapPoint.mockResolvedValue({ pressed: true }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, diff --git a/src/daemon/interaction/internal/__tests__/interaction-touch.test.ts b/src/daemon/interaction/internal/__tests__/interaction-touch.test.ts index ac5e6465bf..ffaf06f45d 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-touch.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-touch.test.ts @@ -18,6 +18,7 @@ import { makeMacOsMenubarSession, makeSession, } from './interaction-touch-fixtures.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; // Router ownership: one representative per touch command proves // handleInteractionCommands claims press/click/longpress/hover/fill. @@ -77,6 +78,7 @@ test('press coordinates dispatches press and records as press', async () => { sessionStore.publish(sessionName, storedSession); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -119,6 +121,7 @@ test.each([ sessionStore.publish(sessionName, session); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, command, positionals: [...positionals], flags: {} }, sessionName, sessionStore, @@ -137,6 +140,7 @@ test('click rejects macOS desktop surface interactions until helper routing exis sessionStore.publish(sessionName, makeMacOsDesktopSession(sessionName)); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -163,6 +167,7 @@ test('fill rejects macOS menubar surface interactions until helper routing exist sessionStore.publish(sessionName, makeMacOsMenubarSession(sessionName)); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -204,6 +209,7 @@ test('longpress @ref resolves the target and dispatches coordinate longpress', a sessionStore.publish(sessionName, session); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -249,6 +255,7 @@ test('hover @ref on web dispatches through the provider hoverRef route, not coor }; sessionStore.publish(sessionName, session); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -292,6 +299,7 @@ test('hover selector on web resolves the target and dispatches coordinate hover' }); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -318,6 +326,7 @@ test('hover is refused by capability on touch platforms before any dispatch', as sessionStore.publish(sessionName, makeSession(sessionName)); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, diff --git a/src/daemon/interaction/internal/__tests__/interaction-type-android-readiness.test.ts b/src/daemon/interaction/internal/__tests__/interaction-type-android-readiness.test.ts index 35d40b2108..4be9cc0a38 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-type-android-readiness.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-type-android-readiness.test.ts @@ -10,6 +10,7 @@ import { mockTypeText, resetGetRuntimeFixture, } from '../../../__tests__/interaction-get-runtime-fixture.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; vi.mock('@agent-device/platform-android/mechanics', async (importOriginal) => { const actual = await importOriginal(); @@ -47,6 +48,7 @@ test('type continues and composes the recording readiness warning', async () => sessionStore.publish(sessionName, session); const response = await handleInteractionCommands({ + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, diff --git a/src/daemon/interaction/internal/find.ts b/src/daemon/interaction/internal/find.ts index a3255356b4..991d022ae0 100644 --- a/src/daemon/interaction/internal/find.ts +++ b/src/daemon/interaction/internal/find.ts @@ -96,6 +96,7 @@ export async function handleFindCommands(params: FindRouteInput): Promise Promise; export type InteractionRuntimeInput = { + createCommandSurface: CreateDaemonCommandSurface; requestId?: string; flags: CommandFlags | undefined; session: InteractionSessionView; diff --git a/src/daemon/request-generic-dispatch.ts b/src/daemon/request-generic-dispatch.ts index e52f8b03ea..04b6099b83 100644 --- a/src/daemon/request-generic-dispatch.ts +++ b/src/daemon/request-generic-dispatch.ts @@ -24,6 +24,7 @@ import { import { isActiveProviderDevice } from './provider-device-admission.ts'; import { buildActionEventResult } from '@agent-device/session-journal/session-event-action-presentation'; import type { AndroidObservationAdapter } from '@agent-device/contracts/android-observation'; +import type { CreateDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; import type { GenericPlatformExecution, RecordedGenericRequest } from './generic-leaf-execution.ts'; export async function dispatchGenericCommand(params: { @@ -38,6 +39,7 @@ export async function dispatchGenericCommand(params: { traceLogPath?: string, ) => DaemonCommandContext; executePlatformCommand: GenericPlatformExecution; + createCommandSurface: CreateDaemonCommandSurface; recordedRequest?: RecordedGenericRequest; androidObservation?: AndroidObservationAdapter; platformServices: DaemonPlatformServices; @@ -65,6 +67,7 @@ export async function dispatchGenericCommand(params: { platformServices: params.platformServices, command: platformCommand, flags: req.flags, + createCommandSurface: params.createCommandSurface, }); if ('response' in settlePlan) return settlePlan.response; const preflightReadiness = await ensureNoAndroidBlockingDialogReady( @@ -206,6 +209,7 @@ function withReadinessWarnings( * without settle, and every non-settle generic leaf, load nothing. */ async function planGenericSettleObservation(params: { + createCommandSurface: CreateDaemonCommandSurface; sessionRef: SessionRef | undefined; req: DaemonRequest; session: SessionState; diff --git a/src/daemon/request-handler-chain.ts b/src/daemon/request-handler-chain.ts index c9cc6f8de2..d8cf91a734 100644 --- a/src/daemon/request-handler-chain.ts +++ b/src/daemon/request-handler-chain.ts @@ -24,6 +24,7 @@ import type { HostDiagnostics } from '@agent-device/contracts/host-diagnostics'; import type { PlatformRequestScope } from '@agent-device/contracts/platform-runtime-host'; import type { RequestPlatformProviderScope } from '@agent-device/contracts/platform-providers'; import type { AndroidObservationAdapter } from '@agent-device/contracts/android-observation'; +import type { CreateDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; import type { PlatformResourceCleanup } from './platform-resource-cleanup.ts'; import type { DaemonProviderCredentials } from '../provider-credential-fingerprint.ts'; @@ -49,6 +50,7 @@ type RequestHandlerChainParams = { */ providerScope: RequestPlatformProviderScope; androidObservation?: AndroidObservationAdapter; + createCommandSurface: CreateDaemonCommandSurface; platformResourceCleanup?: PlatformResourceCleanup; bindDevice: BindDeviceRuntime; platformServices: DaemonPlatformServices; @@ -173,6 +175,7 @@ async function runSessionHandler( sessionName: params.sessionName, logPath: params.logPath, sessionStore: params.sessionStore, + createCommandSurface: params.createCommandSurface, leaseRegistry: params.leaseRegistry, leaseLifecycleProvider: params.leaseLifecycleProvider, providerAppCatalog: params.providerAppCatalog, @@ -209,6 +212,7 @@ async function runSnapshotHandler( sessionName: params.sessionName, logPath: params.logPath, sessionStore: params.sessionStore, + createCommandSurface: params.createCommandSurface, inspectFacts: params.inspectFacts, platformServices: params.platformServices, bindDevice: params.bindDevice, @@ -226,6 +230,7 @@ async function runReactNativeHandler( 'react-native', await handleReactNativeCommands({ req: params.req, + createCommandSurface: params.createCommandSurface, sessionName: params.sessionName, logPath: params.logPath, sessionStore: params.sessionStore, @@ -275,6 +280,7 @@ async function runFindHandler( sessionName: params.sessionName, logPath: params.logPath, sessionStore: params.sessionStore, + createCommandSurface: params.createCommandSurface, invoke: params.invoke, inspectFacts: params.inspectFacts, platformServices: params.platformServices, @@ -295,6 +301,7 @@ async function runInteractionHandler( sessionName: params.sessionName, logPath: params.logPath, sessionStore: params.sessionStore, + createCommandSurface: params.createCommandSurface, contextFromFlags: params.contextFromFlags, inspectFacts: params.inspectFacts, platformServices: params.platformServices, diff --git a/src/daemon/request-router.ts b/src/daemon/request-router.ts index 08b0615685..9b295856a4 100644 --- a/src/daemon/request-router.ts +++ b/src/daemon/request-router.ts @@ -90,6 +90,7 @@ import { resolveGenericRuntimeExecution } from './generic-runtime-execution.ts'; import { discloseRequestDispatch, refusedBeforeDispatch } from './request-dispatch-disclosure.ts'; import { recordNestedRequests } from './request-dispatch-ledger.ts'; import type { AndroidObservationAdapter } from '@agent-device/contracts/android-observation'; +import type { CreateDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; import type { PlatformResourceCleanup } from './platform-resource-cleanup.ts'; import type { DaemonPlatformServices } from './platform-services.ts'; import type { DaemonProviderCredentials } from '../provider-credential-fingerprint.ts'; @@ -132,6 +133,8 @@ export type RequestRouterDeps = { cloudArtifactProvider?: CloudArtifactProvider; providerAppCatalog?: ProviderAppCatalog; androidObservation?: AndroidObservationAdapter; + /** Builds the per-request command surface the device-facing runtimes execute through. */ + createCommandSurface: CreateDaemonCommandSurface; platformResourceCleanup?: PlatformResourceCleanup; providerDeviceRuntimeScope?: (task: () => Promise) => Promise; /** ADR 0029: the daemon policy every admitted request, including nested steps, obeys. */ @@ -192,6 +195,7 @@ export function createRequestHandler(deps: RequestRouterDeps): DaemonInvokeFn { cloudArtifactProvider, providerAppCatalog, androidObservation = unavailableAndroidObservation, + createCommandSurface, platformResourceCleanup = unavailablePlatformResourceCleanup, providerDeviceRuntimeScope, trackDownloadableArtifact, @@ -383,6 +387,7 @@ export function createRequestHandler(deps: RequestRouterDeps): DaemonInvokeFn { : undefined, providerScope, androidObservation, + createCommandSurface, platformResourceCleanup, bindDevice: lockedScope.bindDevice, platformServices: lockedScope.platformServices, @@ -409,6 +414,7 @@ export function createRequestHandler(deps: RequestRouterDeps): DaemonInvokeFn { logPath: lockedScope.logPath, sessionStore, androidObservation, + createCommandSurface, }); } @@ -603,8 +609,9 @@ async function dispatchGenericForLockedScope(params: { logPath: string; sessionStore: SessionStore; androidObservation: AndroidObservationAdapter; + createCommandSurface: CreateDaemonCommandSurface; }): Promise { - const { lockedScope, logPath, sessionStore, androidObservation } = params; + const { lockedScope, logPath, sessionStore, androidObservation, createCommandSurface } = params; const ref = sessionStore.lookup(lockedScope.sessionName); if (!ref) { return noActiveSessionError(); @@ -624,6 +631,7 @@ async function dispatchGenericForLockedScope(params: { inspectFacts: lockedScope.inspectFacts, platformServices: lockedScope.platformServices, bindDevice: lockedScope.bindDevice, + createCommandSurface, }); if (!runtimeExecution.ok) return refusedBeforeDispatch(runtimeExecution.response); @@ -638,6 +646,7 @@ async function dispatchGenericForLockedScope(params: { platformServices: lockedScope.platformServices, executePlatformCommand: runtimeExecution.execute, androidObservation, + createCommandSurface, ...(runtimeExecution.recorded ? { recordedRequest: runtimeExecution.recorded } : {}), }); return dispatchResponse; diff --git a/src/daemon/screenshot-runtime.ts b/src/daemon/screenshot-runtime.ts index ae842c3c1d..bb94bd2f45 100644 --- a/src/daemon/screenshot-runtime.ts +++ b/src/daemon/screenshot-runtime.ts @@ -16,7 +16,7 @@ import path from 'node:path'; import type { AgentDeviceBackend } from '@agent-device/contracts/backend'; import type { ArtifactAdapter } from '@agent-device/contracts/artifact-adapter'; import { localCommandPolicy } from '@agent-device/contracts/command-policy'; -import { createCommandSurfaceAgentDevice } from '../command-runtime/runtime-command-surface.ts'; +import type { CreateDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; import { assertSupportedScreenshotPixelDensity, readScreenshotResultMetadata, @@ -48,6 +48,7 @@ export async function resolveScreenshotGenericExecution( params: Readonly<{ req: DaemonRequest; session: SessionState; + createCommandSurface: CreateDaemonCommandSurface; }> & ScreenshotRuntimeBindings, ): Promise { @@ -82,11 +83,13 @@ export async function resolveScreenshotGenericExecution( const runtime = resolved.runtime; const cropRun: ScreenshotCropRun = { warnings: [] }; + const { createCommandSurface } = params; return { ok: true, recorded: request.recorded, execute: async (execution) => await executeScreenshot({ + createCommandSurface, session: execution.session, sessionName: execution.sessionName, logPath: execution.logPath, @@ -107,6 +110,7 @@ export async function resolveScreenshotGenericExecution( */ export async function captureScreenshotArtifact( params: Readonly<{ + createCommandSurface: CreateDaemonCommandSurface; session: SessionState; sessionName: string; outPath?: string; @@ -116,7 +120,7 @@ export async function captureScreenshotArtifact( }>, ): Promise { const { session, sessionName, outPath, dispatchContext } = params; - const runtime = createCommandSurfaceAgentDevice({ + const runtime = params.createCommandSurface({ backend: createBoundScreenshotBackend(params), artifacts: createDaemonScreenshotArtifactAdapter(), sessions: createReadonlyRuntimeSessionStore(sessionName, session), @@ -172,6 +176,7 @@ export function screenshotExecutionFromContext( async function executeScreenshot( params: Readonly<{ + createCommandSurface: CreateDaemonCommandSurface; session: SessionState; sessionName: string; logPath: string; @@ -191,6 +196,7 @@ async function executeScreenshot( logPath: params.logPath, }); const captured = await captureScreenshotArtifact({ + createCommandSurface: params.createCommandSurface, session, sessionName: params.sessionName, outPath: params.outPath, diff --git a/src/daemon/selector-runtime-backend.ts b/src/daemon/selector-runtime-backend.ts index 59d6a28f4b..db8900a4fa 100644 --- a/src/daemon/selector-runtime-backend.ts +++ b/src/daemon/selector-runtime-backend.ts @@ -5,7 +5,7 @@ import type { BackendSnapshotResult, } from '@agent-device/contracts/backend'; import { resolveTargetDevice } from '@agent-device/device-selection/dispatch-resolve'; -import { createCommandSurfaceAgentDevice } from '../command-runtime/runtime-command-surface.ts'; +import type { CreateDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; import { publicPlatformString } from '@agent-device/kernel/device'; import type { SnapshotState, SnapshotNode } from '@agent-device/kernel/snapshot'; import { createDaemonRuntimePolicy } from './runtime-policy.ts'; @@ -34,6 +34,8 @@ import { checkIsArgs } from '@agent-device/selectors'; import { noActiveSessionError } from '@agent-device/kernel/contracts'; export type SelectorRuntimeParams = { + /** Builds the command surface this request's selector runtime executes through. */ + createCommandSurface: CreateDaemonCommandSurface; req: DaemonRequest; sessionName: string; logPath?: string; @@ -83,7 +85,7 @@ type ResolvedSelectorDevice = | { ok: false; response: DaemonResponse }; export function createSelectorRuntimeForDevice(params: SelectorRuntimeDeviceParams) { - return createCommandSurfaceAgentDevice({ + return params.createCommandSurface({ backend: createSelectorBackend(params), ...createDaemonRuntimePolicy('selector commands', { plural: true }), sessions: createDaemonRuntimeSessionStore({ diff --git a/src/daemon/server/daemon-runtime-idle-reap.test.ts b/src/daemon/server/daemon-runtime-idle-reap.test.ts index 8174fc2f49..e361cdd698 100644 --- a/src/daemon/server/daemon-runtime-idle-reap.test.ts +++ b/src/daemon/server/daemon-runtime-idle-reap.test.ts @@ -1,6 +1,7 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; import { afterEach, test, vi } from 'vitest'; +import { testCreateCommandSurface } from '../__tests__/command-surface-fixture.ts'; const leaseProbe = vi.hoisted(() => ({ registries: [] as import('../lease-registry.ts').LeaseRegistry[], @@ -37,6 +38,7 @@ test('daemon runtime self-reaps after the idle window when nothing ever uses it' try { const runtime = await startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -76,6 +78,7 @@ test('daemon runtime never self-reaps when AGENT_DEVICE_DAEMON_IDLE_TIMEOUT_MS i try { const runtime = await startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -113,6 +116,7 @@ test('only a retained lease keeps the daemon runtime from self-reaping', async ( try { const runtime = await startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, diff --git a/src/daemon/server/daemon-runtime-interactor-composition.test.ts b/src/daemon/server/daemon-runtime-interactor-composition.test.ts index af6d730cca..6a1cf2180d 100644 --- a/src/daemon/server/daemon-runtime-interactor-composition.test.ts +++ b/src/daemon/server/daemon-runtime-interactor-composition.test.ts @@ -13,6 +13,7 @@ import { } from '@agent-device/daemon-contracts/daemon-registration-owner'; import { resolveDaemonPaths } from '@agent-device/daemon-contracts/daemon-resolution'; import { interactorResolution } from '../interactor-resolution.ts'; +import { testCreateCommandSurface } from '../__tests__/command-surface-fixture.ts'; vi.mock('../../platform-runtime.ts', () => ({ androidObservation: {}, @@ -60,6 +61,7 @@ test('daemon startup composes the interactor resolution the daemon resolves thro const providerInteractor = { snapshot: async () => ({ nodes: [] }) } as unknown as Interactor; try { const runtime = await startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -94,6 +96,7 @@ test('daemon startup compares lease credentials with its own startup environment const stateDir = mkdtempForTestSync('agent-device-daemon-credential-composition-'); const daemonEnv = { BROWSERSTACK_USERNAME: 'user', BROWSERSTACK_ACCESS_KEY: 'key-1' }; const runtime = await startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { ...process.env, ...daemonEnv, @@ -160,6 +163,7 @@ test('a daemon attempt losing the lock shuts down every constructed provider', a }); const exit = vi.fn(); const runtime = await startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { AGENT_DEVICE_STATE_DIR: stateDir }, exit, registerProcessHandlers: false, diff --git a/src/daemon/server/daemon-runtime-lifecycle-shutdown.test.ts b/src/daemon/server/daemon-runtime-lifecycle-shutdown.test.ts index 1730253d16..111af1a5b9 100644 --- a/src/daemon/server/daemon-runtime-lifecycle-shutdown.test.ts +++ b/src/daemon/server/daemon-runtime-lifecycle-shutdown.test.ts @@ -2,6 +2,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { afterEach, expect, test, vi } from 'vitest'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../__tests__/command-surface-fixture.ts'; const lifecycleEvents = vi.hoisted(() => [] as string[]); @@ -154,6 +155,7 @@ test('daemon shutdown detaches before session teardown and force-finalizes only const startupErrors: string[] = []; try { const runtime = await startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -182,6 +184,7 @@ test('a SIGTERM shutdown gives the handoff a diagnostics scope to write its reas const stateDir = mkdtempForTestSync('agent-device-daemon-detach-diagnostics-'); try { const runtime = await startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -210,6 +213,7 @@ const claimStores = isolatedDeviceClaimStores('daemon-drain-admission-'); test('shutdown includes drain publications, releases their claims and refuses post-snapshot publication', async () => { const { stateDir } = claimStores(); const runtime = await startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -264,6 +268,7 @@ test('shutdown includes drain publications, releases their claims and refuses po test('shutdown joins dispatch completion after force-closing the client before taking its snapshot', async () => { const { stateDir } = claimStores(); const runtime = await startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -402,6 +407,7 @@ test.each([false, true])( test('daemon shutdown releases a session lease whose session teardown rejects', async () => { const stateDir = mkdtempForTestSync('agent-device-daemon-session-lease-shutdown-'); const runtime = await startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -436,6 +442,7 @@ test('daemon shutdown releases a retainOnClose lease that no session holds', asy const stateDir = mkdtempForTestSync('agent-device-daemon-retained-lease-shutdown-'); try { const runtime = await startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, diff --git a/src/daemon/server/daemon-runtime-metadata-ownership.test.ts b/src/daemon/server/daemon-runtime-metadata-ownership.test.ts index f195effdfc..df91edadb3 100644 --- a/src/daemon/server/daemon-runtime-metadata-ownership.test.ts +++ b/src/daemon/server/daemon-runtime-metadata-ownership.test.ts @@ -3,6 +3,7 @@ import path from 'node:path'; import { afterEach, expect, test, vi } from 'vitest'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; import { publishDaemonRegistration } from '@agent-device/daemon-contracts/daemon-registration-fixtures'; +import { testCreateCommandSurface } from '../__tests__/command-surface-fixture.ts'; const lifecycleEvents = vi.hoisted(() => [] as string[]); const startupFailure = vi.hoisted(() => ({ active: false })); @@ -68,7 +69,8 @@ const SUCCESSOR_PID = 999_999_999; function startRuntime(stateDir: string, exit: (code: number) => void) { return startDaemonRuntime({ - env: { + + createCommandSurface: testCreateCommandSurface,env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, AGENT_DEVICE_DAEMON_IDLE_TIMEOUT_MS: '0', diff --git a/src/daemon/server/daemon-runtime-recording-reaper.test.ts b/src/daemon/server/daemon-runtime-recording-reaper.test.ts index dc8fecc065..c64fde7db4 100644 --- a/src/daemon/server/daemon-runtime-recording-reaper.test.ts +++ b/src/daemon/server/daemon-runtime-recording-reaper.test.ts @@ -1,6 +1,7 @@ import fs from 'node:fs'; import { afterEach, expect, test, vi } from 'vitest'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../__tests__/command-surface-fixture.ts'; const reapCalls = vi.hoisted(() => [] as Array>); @@ -52,6 +53,7 @@ test('daemon startup reaps orphaned simctl recorders with a graceful finalize wi const stateDir = mkdtempForTestSync('agent-device-daemon-recording-reaper-'); try { const runtime = await startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, diff --git a/src/daemon/server/daemon-runtime-snapshot-shutdown.test.ts b/src/daemon/server/daemon-runtime-snapshot-shutdown.test.ts index d5e0193395..dab0647cb8 100644 --- a/src/daemon/server/daemon-runtime-snapshot-shutdown.test.ts +++ b/src/daemon/server/daemon-runtime-snapshot-shutdown.test.ts @@ -13,6 +13,7 @@ import { platformResourceCleanup } from '../../platform-runtime-resource-cleanup import { DAEMON_SESSION_TEARDOWN_TIMEOUT_MS } from '../session-teardown-budget.ts'; import { dispatchSnapshotViaRuntime } from '../snapshot-runtime.ts'; import { teardownDaemonSessionForShutdown } from './daemon-runtime.ts'; +import { testCreateCommandSurface } from '../__tests__/command-surface-fixture.ts'; vi.mock('@agent-device/device-selection/dispatch-resolve', () => ({ resolveTargetDevice: vi.fn(async () => ANDROID_EMULATOR), @@ -64,6 +65,7 @@ for (const initialSession of ['published', 'draft'] as const) { await releaseCleanup.promise; }); const running = dispatchSnapshotViaRuntime({ + createCommandSurface: testCreateCommandSurface, req: { command: 'snapshot', positionals: [], diff --git a/src/daemon/server/daemon-runtime-xctest-device-set.test.ts b/src/daemon/server/daemon-runtime-xctest-device-set.test.ts index 87f71b9cbd..22cbc09d45 100644 --- a/src/daemon/server/daemon-runtime-xctest-device-set.test.ts +++ b/src/daemon/server/daemon-runtime-xctest-device-set.test.ts @@ -3,6 +3,7 @@ import path from 'node:path'; import { afterEach, expect, test, vi } from 'vitest'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; import { withMockedPlatform } from '../../__tests__/test-utils/host-execution.ts'; +import { testCreateCommandSurface } from '../__tests__/command-surface-fixture.ts'; const legacyRedirect = vi.hoisted(() => ({ xctestDeviceSetPath: '', @@ -74,6 +75,7 @@ function loggedEvents(logPath: string): Array<{ phase: string; data?: Record startDaemonRuntime({ + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, diff --git a/src/daemon/server/daemon-runtime.ts b/src/daemon/server/daemon-runtime.ts index 09bf431ec5..6bde5986a1 100644 --- a/src/daemon/server/daemon-runtime.ts +++ b/src/daemon/server/daemon-runtime.ts @@ -18,6 +18,7 @@ import { loadDaemonPolicy, type DaemonPolicy, } from '@agent-device/daemon-contracts/daemon-policy-file'; +import type { CreateDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; import { getInteractor } from '../../core/interactors.ts'; import { installInteractorResolution } from '../interactor-resolution.ts'; import { @@ -211,6 +212,8 @@ export type DaemonRuntimeOptions = { stderr?: WritableOutput; exit?: (code: number) => void; registerProcessHandlers?: boolean; + /** The command-surface factory the request handler builds per-request runtimes from. */ + createCommandSurface: CreateDaemonCommandSurface; }; export type DaemonRuntimeController = { @@ -319,7 +322,7 @@ function armDaemonMetadataLossWatch( } export async function startDaemonRuntime( - options: DaemonRuntimeOptions = {}, + options: DaemonRuntimeOptions, ): Promise { const env = options.env ?? process.env; const stdout = options.stdout ?? process.stdout; @@ -451,6 +454,7 @@ export async function startDaemonRuntime( providerDeviceRuntimeScope: providerRuntimeProviders.providerDeviceRuntimeScope, trackDownloadableArtifact, daemonPolicy, + createCommandSurface: options.createCommandSurface, }); let stopMetadataLossWatch: () => void = () => {}; diff --git a/src/daemon/session-lifecycle/__tests__/application.test.ts b/src/daemon/session-lifecycle/__tests__/application.test.ts index f60168c1d0..d344552ae7 100644 --- a/src/daemon/session-lifecycle/__tests__/application.test.ts +++ b/src/daemon/session-lifecycle/__tests__/application.test.ts @@ -3,6 +3,7 @@ import { beforeEach, expect, test, vi } from 'vitest'; import type { DaemonRequest, DaemonResponse } from '../../daemon-request.ts'; import { makeSessionStore } from '../../../__tests__/test-utils/store-factory.ts'; import { sessionOrDeviceSelectorRequiredDetails } from '@agent-device/kernel/errors'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; vi.mock('@agent-device/device-selection/device-inventory-context', async (importOriginal) => { const actual = @@ -51,7 +52,8 @@ function request(command: DaemonRequest['command']): DaemonRequest { async function run(command: DaemonRequest['command']): Promise { return await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: request(command), sessionName: 'default', logPath: '/tmp/agent-device-session-lifecycle-route.log', @@ -109,6 +111,7 @@ test('open routes only its lifecycle input through the public facade', async () expect(Object.keys(forwarded ?? {}).sort()).toEqual( [ 'bindDevice', + 'createCommandSurface', 'inspectFacts', 'logPath', 'platformServices', diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-capabilities-install-projection.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-capabilities-install-projection.test.ts index 8152f61409..e0460aa683 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-capabilities-install-projection.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-capabilities-install-projection.test.ts @@ -12,6 +12,7 @@ import type { import { createCapabilitiesAdmissionRuntime } from './session-capabilities.fixtures.ts'; import { handleSessionCommands } from '../../../handlers/__tests__/session-command-harness.ts'; import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; test('capabilities projects the install family from exactly one facts inspection', async () => { const { sessionName, sessionStore } = createAndroidCapabilitiesSession('install-family'); @@ -154,7 +155,8 @@ async function dispatchCapabilities(params: { inspectFacts: InspectDeviceRuntimeFacts; }) { return await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: params.sessionName, diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-capabilities.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-capabilities.test.ts index 4df3e81b28..5c10a0f7ad 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-capabilities.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-capabilities.test.ts @@ -33,6 +33,7 @@ import type { } from '../../../request-runtime-binding.ts'; import { handleSessionCommands } from '../../../handlers/__tests__/session-command-harness.ts'; import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; /** The system leaves this owner refuses: the retired fallback listed them unconditionally. */ const ANDROID_REFUSED_SYSTEM_COMMANDS = ['clipboard', 'alert', 'settings', 'app-switcher']; @@ -61,7 +62,8 @@ async function projectAndroidCapabilities(sessionName: string) { providerMode: 'local', }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -144,7 +146,8 @@ test('capabilities excludes logs from an unavailable provider-mode XCTest runtim }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -188,7 +191,8 @@ test('capabilities excludes network when the runtime fact is unavailable', async }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -233,7 +237,8 @@ test('capabilities includes apps for the available HarmonyOS runtime fact', asyn async (request) => (request.platform === 'harmonyos' ? [harmonyDevice] : []), async () => await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -312,7 +317,8 @@ test.each(APPS_UNAVAILABLE_CAPABILITY_CASES)( }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -347,7 +353,8 @@ test('capabilities excludes appstate when its runtime fact is unavailable', asyn }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -381,7 +388,8 @@ test('capabilities excludes appstate when its readiness fact is unavailable', as }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -457,7 +465,8 @@ test.each([ async () => [device], async () => await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -494,7 +503,8 @@ test('capabilities accepts a stopped Android AVD placeholder for explicit platfo async (request) => (request.platform === 'android' ? [stoppedAvd] : []), async () => await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-close-cwd-scoped-resources.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-close-cwd-scoped-resources.test.ts index 7c230ac081..416a578ad6 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-close-cwd-scoped-resources.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-close-cwd-scoped-resources.test.ts @@ -16,6 +16,7 @@ import { } from './session-close-shutdown.fixtures.ts'; import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; import { encodeHostAudioProbeDescriptor } from '@agent-device/capture-kit/audio-probe-descriptor-fixtures'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; const { handleSessionCommands, @@ -52,7 +53,8 @@ const IOS_SIM: SessionState['device'] = { async function closeAtAddress(sessionStore: ReturnType) { return await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: ADDRESS, command: 'close', positionals: [], flags: {} }, sessionName: ADDRESS, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-close-error-precedence.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-close-error-precedence.test.ts index cc66dca3e2..7cf12fce8b 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-close-error-precedence.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-close-error-precedence.test.ts @@ -4,6 +4,7 @@ import { sessionCloseShutdownFixture, type SessionState, } from './session-close-shutdown.fixtures.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; const { acquireDeviceClaim, @@ -49,7 +50,8 @@ test('targeted close preserves the platform-close AppError and still runs later await expect( handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -128,7 +130,8 @@ test('a failed platform close retains the device claim and reports it', async () let caught: unknown; try { await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -242,7 +245,8 @@ test('a failing best-effort cleanup also retains the device claim and reports it let caught: unknown; try { await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -328,7 +332,8 @@ test('a successful close clears the device claim', async () => { sessionStore.publish(sessionName, session); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -381,7 +386,8 @@ test('targeted close skips platform dispatch and preserves the error when the re await expect( handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-close-lifecycle-runtime.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-close-lifecycle-runtime.test.ts index a3e3587f78..65390b2ba1 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-close-lifecycle-runtime.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-close-lifecycle-runtime.test.ts @@ -10,6 +10,7 @@ import { readSessionRuntimeRevision, refFrameState, } from '../../../ref-frame.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; const runtimeHintsModule = vi.hoisted(() => ({ evaluated: false, @@ -64,7 +65,8 @@ async function close(params: { internal?: DaemonRequest['internal']; }) { return await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: closeRequest(params.sessionName, params.positionals ?? [], params.internal), sessionName: params.sessionName, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-close-resource-cleanup.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-close-resource-cleanup.test.ts index 0f563d5859..c434c9cf42 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-close-resource-cleanup.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-close-resource-cleanup.test.ts @@ -9,6 +9,7 @@ import { } from './session-close-shutdown.fixtures.ts'; import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; import { encodeHostAudioProbeDescriptor } from '@agent-device/capture-kit/audio-probe-descriptor-fixtures'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; const { AppError, @@ -43,7 +44,8 @@ test('close stops Android snapshot helper session before deleting session', asyn }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -139,7 +141,8 @@ test('close stops active host audio probe before deleting session', async () => ); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -164,7 +167,8 @@ test('close dispatches web session cleanup without a positional target', async ( sessionStore.publish(sessionName, makeSession(sessionName, WEB_DESKTOP_DEVICE)); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -216,6 +220,7 @@ test('close preserves the session and lease when provider release fails so it ca let releaseAttempts = 0; const request = { + createCommandSurface: testCreateCommandSurface, req: { token: 't', session: sessionName, @@ -295,7 +300,8 @@ test('close cannot retire a replacement session while its provider release waits return { releasedBy: 'provider' }; }); const closing = handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: address, command: 'close', positionals: [], flags: {} }, sessionName: address, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-close-save-script.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-close-save-script.test.ts index 8f9bd57ff8..174cc1389e 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-close-save-script.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-close-save-script.test.ts @@ -2,6 +2,7 @@ import { daemonPlatformServicesFixture } from '../../../__tests__/platform-servi import { beforeEach, expect, test } from 'vitest'; import { sessionCloseShutdownFixture } from './session-close-shutdown.fixtures.ts'; import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; const { fs, @@ -37,7 +38,8 @@ test('close --save-script on a never-armed session is rejected before teardown, await expect( handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -72,7 +74,8 @@ test('close --save-script on a never-armed session is rejected before teardown, // A plain close (no --save-script) still closes the same session cleanly afterward, and now // teardown genuinely does run: the recorder is signaled and the session deleted. const plainClose = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -112,7 +115,8 @@ test('close --save-script on a session with an active .ad repair transaction is sessionStore.publish(sessionName, session); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-close-shutdown-platform.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-close-shutdown-platform.test.ts index d384ba045c..b08dfad60f 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-close-shutdown-platform.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-close-shutdown-platform.test.ts @@ -6,6 +6,7 @@ import { type PlatformRuntimeOperations, } from './session-close-shutdown.fixtures.ts'; import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; const { handleSessionCommands, @@ -72,7 +73,8 @@ test('close --shutdown calls shutdownSimulator for iOS simulator and includes re }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -138,7 +140,8 @@ test('close --shutdown keeps a selected provider-owned iOS simulator off local s }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -181,7 +184,8 @@ test('close --shutdown calls shutdownAndroidEmulator for Android emulator and in }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -225,7 +229,8 @@ test('close --shutdown is ignored for non-simulator iOS devices', async () => { ); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -264,7 +269,8 @@ test('close --shutdown is ignored for Android devices', async () => { ); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -311,7 +317,8 @@ test('close --shutdown returns success and failure payload when shutdownAndroidE }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -352,7 +359,8 @@ test('close --shutdown returns success and failure payload when shutdownSimulato }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-devices-batch-runtime.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-devices-batch-runtime.test.ts index b931126845..33a16657bb 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-devices-batch-runtime.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-devices-batch-runtime.test.ts @@ -2,6 +2,7 @@ import { daemonPlatformServicesFixture } from '../../../__tests__/platform-servi import { test, expect, vi } from 'vitest'; import * as fs from 'node:fs'; import * as path from 'node:path'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; vi.mock('../../../materialized-path-registry.ts', async (importOriginal) => { const actual = await importOriginal(); @@ -389,7 +390,8 @@ test('close clears retained materialized install paths bound to the session', as mockCleanupRetainedMaterializedPaths.mockImplementation(realCleanup); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, command: 'close', positionals: [], flags: {} }, sessionName, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-open-execution-runtime.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-open-execution-runtime.test.ts index df14d19968..a9b89fe4e8 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-open-execution-runtime.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-open-execution-runtime.test.ts @@ -15,6 +15,7 @@ import { clearRequestAbortRegistration, } from '@agent-device/host-kit/request'; import { AppError } from '@agent-device/kernel/errors'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; const mockResolveTargetDevice = vi.hoisted(() => vi.fn()); @@ -131,7 +132,8 @@ test('open runtime payload replaces stored session runtime atomically', async () }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -211,7 +213,8 @@ test('open runtime payload clears stale applied transport hints before launch', }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -263,7 +266,8 @@ test('open runtime payload rejects invalid metro port before app launch', async }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'runtime-open-invalid-port', @@ -304,7 +308,8 @@ test('open runtime payload rejects malformed runtime objects without mutating se mockResolveTargetDevice.mockResolvedValue(makeAndroidEmulator()); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -348,7 +353,8 @@ test('open runtime payload does not persist replacement when launch fails', asyn await expect( handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -390,7 +396,8 @@ test('a first open keeps both positionals so the deep link still reaches the app }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'deep-link-open', @@ -435,7 +442,8 @@ test('open reports the launch confirmation its platform answered', async () => { }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'launch-confirmation-open', @@ -477,7 +485,8 @@ async function openReportingObservation( }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -546,7 +555,8 @@ function invokeHeldOpen( req?: Partial, ) { return handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', command: 'open', diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-open-existing.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-open-existing.test.ts index 04d31b60eb..6d3d2caa49 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-open-existing.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-open-existing.test.ts @@ -17,6 +17,7 @@ import { import type { SessionState } from '../../../session-state.ts'; import { handleSessionCommands } from '../../../handlers/__tests__/session-command-harness.ts'; import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; test('open web URL on iOS device session without active app falls back to Safari', async () => { const sessionStore = makeSessionStore(); @@ -39,7 +40,8 @@ test('open web URL on iOS device session without active app falls back to Safari }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -90,7 +92,8 @@ test('open web URL on iOS simulator session without active app falls back to Saf }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -136,7 +139,8 @@ test('open app and URL on existing iOS device session keeps app context', async }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -184,7 +188,8 @@ test('open app on existing macOS session resolves and stores bundle id', async ( }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -217,7 +222,8 @@ test('open rejects --surface on non-macOS devices', async () => { }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'ios-surface', @@ -269,7 +275,8 @@ test('open on existing macOS frontmost-app session preserves surface without --s try { const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -338,7 +345,8 @@ test('open on existing iOS session refreshes unavailable simulator by name', asy const response = await withMockedPlatform('darwin', async () => handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -388,7 +396,8 @@ test('open app on existing Android session resolves and stores package id', asyn mockResolveAndroidPackage.mockResolvedValue('org.reactjs.native.example.RNCLI83'); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -433,7 +442,8 @@ test('open intent target on existing Android session clears stale package contex mockResolveAndroidPackage.mockResolvedValue(undefined); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -487,7 +497,8 @@ test('open on existing Android session preserves a comparable freshness baseline mockResolveAndroidPackage.mockResolvedValue('com.android.settings'); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-open-foreground.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-open-foreground.test.ts index a4169e20c0..424e9e058d 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-open-foreground.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-open-foreground.test.ts @@ -1,5 +1,6 @@ import { daemonPlatformServicesFixture } from '../../../__tests__/platform-services-fixture.ts'; import { beforeEach, expect, test, vi } from 'vitest'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; const dispatchSnapshotViaRuntime = vi.hoisted(() => vi.fn()); @@ -151,7 +152,8 @@ const failedOpenResponse: DaemonResponse = { test('passes a failed open response through untouched', async () => { const result = await composeOpenWithInitialSnapshot({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: baseRequest({ flags: { foreground: true } }), ref, logPath: '/tmp/daemon.log', @@ -167,7 +169,8 @@ test('passes a failed open response through untouched', async () => { test('leaves a successful open response untouched when --foreground was not requested', async () => { const result = await composeOpenWithInitialSnapshot({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: baseRequest(), ref, logPath: '/tmp/daemon.log', @@ -186,7 +189,8 @@ test('attaches the initial INTERACTIVE snapshot by delegating to the existing sn const req = baseRequest({ flags: { foreground: true }, positionals: ['xyz.blueskyweb.app'] }); const result = await composeOpenWithInitialSnapshot({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req, ref, logPath: '/tmp/daemon.log', @@ -199,6 +203,7 @@ test('attaches the initial INTERACTIVE snapshot by delegating to the existing sn // #1670 P1: the composed dispatch must BE the `snapshot -i` path — the CLI // maps `-i` to snapshotInteractiveOnly, the key the snapshot runtime reads. expect(dispatchSnapshotViaRuntime).toHaveBeenCalledWith({ + createCommandSurface: testCreateCommandSurface, req: { ...req, command: 'snapshot', @@ -238,7 +243,8 @@ test('a snapshot-capture failure never masks the successful open', async () => { dispatchSnapshotViaRuntime.mockResolvedValue(snapshotFailure); const result = await composeOpenWithInitialSnapshot({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: baseRequest({ flags: { foreground: true } }), ref, logPath: '/tmp/daemon.log', @@ -281,7 +287,8 @@ test('a THROWN snapshot-capture failure never masks the successful open either', ); const result = await composeOpenWithInitialSnapshot({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: baseRequest({ flags: { foreground: true } }), ref, logPath: '/tmp/daemon.log', diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-open-runtime.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-open-runtime.test.ts index e28c074117..bc003a3784 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-open-runtime.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-open-runtime.test.ts @@ -2,6 +2,7 @@ import { daemonPlatformServicesFixture } from '../../../__tests__/platform-servi import { test, expect, vi, beforeEach } from 'vitest'; import path from 'node:path'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; const mockResolveTargetDevice = vi.hoisted(() => vi.fn()); @@ -124,7 +125,8 @@ test('open applies stored runtime launchUrl and reports runtime hints', async () }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'runtime-open', @@ -188,7 +190,8 @@ test('open rejects a false runtime-hints fact before its one implementation bind }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'runtime-open-false-fact', @@ -240,7 +243,8 @@ test('open applies launch-only flags only to the direct app launch before runtim }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'launch-console-runtime', @@ -283,7 +287,8 @@ test('open --metro-port alone defaults the host to 10.0.2.2 on an Android emulat }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -315,7 +320,8 @@ test('open --metro-port alone defaults the host to 127.0.0.1 on an iOS simulator }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -347,7 +353,8 @@ test('open --metro-port alone stays host-ambiguous on a physical Android device' }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -379,7 +386,8 @@ test('open --relaunch allows Android package names ending with apk-like suffix', }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-open-url-prewarm.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-open-url-prewarm.test.ts index 4a3aa559fc..0f85c6466b 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-open-url-prewarm.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-open-url-prewarm.test.ts @@ -1,6 +1,7 @@ import { test, expect, vi, beforeEach } from 'vitest'; import * as path from 'node:path'; import { AppError } from '@agent-device/kernel/errors'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; vi.mock('node:timers/promises', async (importOriginal) => { const actual = await importOriginal(); @@ -102,6 +103,7 @@ function createHandler( deviceRuntimeGateway = lifecycleDeviceRuntimeGateway, ) { return createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-teardown-resources.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-teardown-resources.test.ts index c264b9a674..dabbeedf9f 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-teardown-resources.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-teardown-resources.test.ts @@ -6,6 +6,7 @@ import { } from './session-close-shutdown.fixtures.ts'; import { installFakeManagedAgentBrowser } from '../../../../__tests__/test-utils/web-managed-agent-browser.ts'; import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; const { AppError, @@ -42,7 +43,8 @@ test('close finalizes an active iOS simulator recording before deleting the sess sessionStore.publish(sessionName, session); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -81,7 +83,8 @@ test('close surfaces a recording finalization failure through the cleanup-failur await expect( handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, diff --git a/src/daemon/session-lifecycle/internal/session-open-foreground.ts b/src/daemon/session-lifecycle/internal/session-open-foreground.ts index da558269fe..9a59edec2e 100644 --- a/src/daemon/session-lifecycle/internal/session-open-foreground.ts +++ b/src/daemon/session-lifecycle/internal/session-open-foreground.ts @@ -1,5 +1,6 @@ import type { DaemonPlatformServices } from '../../platform-services.ts'; import { normalizeError, type NormalizedError } from '@agent-device/kernel/errors'; +import type { CreateDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; import { dispatchSnapshotViaRuntime } from '../../snapshot-runtime.ts'; import type { SessionRef } from '../../session-state.ts'; import type { SessionStore } from '../../session-store.ts'; @@ -103,6 +104,7 @@ export async function resolveForegroundOpenRequest(params: { * telling the caller the session IS open and how to capture manually. */ export async function composeOpenWithInitialSnapshot(params: { + createCommandSurface: CreateDaemonCommandSurface; req: DaemonRequest; ref: SessionRef; logPath: string; @@ -117,6 +119,7 @@ export async function composeOpenWithInitialSnapshot(params: { try { const snapshotResponse = await dispatchSnapshotViaRuntime({ + createCommandSurface: params.createCommandSurface, req: { ...req, command: 'snapshot', diff --git a/src/daemon/session-lifecycle/internal/session-open.ts b/src/daemon/session-lifecycle/internal/session-open.ts index 7ae7ac96be..2d20864f20 100644 --- a/src/daemon/session-lifecycle/internal/session-open.ts +++ b/src/daemon/session-lifecycle/internal/session-open.ts @@ -47,8 +47,10 @@ import { } from './session-open-execution.ts'; import { requireOpenSessionAdmission } from './session-open-state.ts'; import { errorResponse } from '@agent-device/kernel/contracts'; +import type { CreateDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; export type SessionOpenCommandInput = Readonly<{ + createCommandSurface: CreateDaemonCommandSurface; req: DaemonRequest; sessionName: string; logPath: string; diff --git a/src/daemon/session-observability/__tests__/application.test.ts b/src/daemon/session-observability/__tests__/application.test.ts index 8c4166f4ea..17d7b5cb19 100644 --- a/src/daemon/session-observability/__tests__/application.test.ts +++ b/src/daemon/session-observability/__tests__/application.test.ts @@ -2,6 +2,7 @@ import { daemonPlatformServicesFixture } from '../../__tests__/platform-services import { beforeEach, expect, test, vi } from 'vitest'; import type { DaemonRequest, DaemonResponse } from '../../daemon-request.ts'; import { makeSessionStore } from '../../../__tests__/test-utils/store-factory.ts'; +import { testCreateCommandSurface } from '../../__tests__/command-surface-fixture.ts'; vi.mock('../index.ts', async (importOriginal) => { const actual = await importOriginal(); @@ -32,7 +33,8 @@ function request(command: DaemonRequest['command']): DaemonRequest { async function run(command: DaemonRequest['command']): Promise { return await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: request(command), sessionName: 'default', logPath: '/tmp/agent-device-session-observability-route.log', diff --git a/src/daemon/session-observability/internal/__tests__/session-logs.test.ts b/src/daemon/session-observability/internal/__tests__/session-logs.test.ts index 396dfb7e73..6d18339d31 100644 --- a/src/daemon/session-observability/internal/__tests__/session-logs.test.ts +++ b/src/daemon/session-observability/internal/__tests__/session-logs.test.ts @@ -29,6 +29,7 @@ import { makeTestAppLogResource, noopInvoke, } from '../../../handlers/__tests__/session-test-harness.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; const DEVICE: DeviceInfo = { platform: 'apple', @@ -297,7 +298,8 @@ async function runLogs( throwIfCanceled?: () => void, ) { return await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, command: 'logs', positionals, flags }, sessionName, logPath: '/tmp/daemon.log', diff --git a/src/daemon/session-observability/internal/__tests__/session-network-runtime-parity.test.ts b/src/daemon/session-observability/internal/__tests__/session-network-runtime-parity.test.ts index 677dbb3a19..a8ff65f847 100644 --- a/src/daemon/session-observability/internal/__tests__/session-network-runtime-parity.test.ts +++ b/src/daemon/session-observability/internal/__tests__/session-network-runtime-parity.test.ts @@ -12,6 +12,7 @@ import { } from '../../../handlers/__tests__/session-test-harness.ts'; import { NETWORK_RUNTIME_PROJECTION_PARITY } from './session-network-runtime-parity-fixtures.ts'; import { createNetworkRuntime, emptyAppLogResult } from './network-runtime-harness.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; test.each(NETWORK_RUNTIME_PROJECTION_PARITY)( 'network $action preserves the $include projection through the request runtime', @@ -60,7 +61,8 @@ test.each(NETWORK_RUNTIME_PROJECTION_PARITY)( const runtime = createNetworkRuntime(session.device, async () => result); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -114,7 +116,8 @@ test('network admission remains fail-closed before parsing invalid input', async }); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: session.name, @@ -212,7 +215,8 @@ async function runNetwork( bindDevice: BindDeviceRuntime, ) { return await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, diff --git a/src/daemon/session-observability/internal/__tests__/session-network.test.ts b/src/daemon/session-observability/internal/__tests__/session-network.test.ts index 40df394236..d83885cb8c 100644 --- a/src/daemon/session-observability/internal/__tests__/session-network.test.ts +++ b/src/daemon/session-observability/internal/__tests__/session-network.test.ts @@ -8,12 +8,14 @@ import { makeSessionStore, noopInvoke, } from '../../../handlers/__tests__/session-test-harness.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; test('network requires an active session before requesting a runtime binding', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'missing', @@ -47,7 +49,8 @@ test('network validates the legacy limit after runtime-fact admission', async () ); const response = await handleSessionCommands({ - platformServices: daemonPlatformServicesFixture(), + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: session.name, diff --git a/src/daemon/snapshot-command-runtime.ts b/src/daemon/snapshot-command-runtime.ts index a2ff55db18..c48ab74efa 100644 --- a/src/daemon/snapshot-command-runtime.ts +++ b/src/daemon/snapshot-command-runtime.ts @@ -9,7 +9,7 @@ import { publicPlatformString } from '@agent-device/kernel/device'; import { AppError } from '@agent-device/kernel/errors'; import type { AgentDeviceBackend, BackendSnapshotResult } from '@agent-device/contracts/backend'; import type { CommandSessionRecord } from '@agent-device/contracts/runtime-contract'; -import { createCommandSurfaceAgentDevice } from '../command-runtime/runtime-command-surface.ts'; +import type { CreateDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; import { getRequestSignal } from '@agent-device/host-kit/request'; import { maybeBuildAndroidSnapshotTimeoutFailure } from './android-snapshot-timeout-evidence.ts'; import { captureSnapshot } from './snapshot-capture.ts'; @@ -67,6 +67,7 @@ export async function dispatchSnapshotRuntimeCommand( const sessionName = ref?.address ?? params.sessionName; const capturedQuality: CapturedSnapshotQuality = {}; const { runtime, sessions } = createSnapshotRuntime({ + createCommandSurface: params.createCommandSurface, req, sessionName, logPath, @@ -123,6 +124,7 @@ export async function dispatchSnapshotRuntimeCommand( } function createSnapshotRuntime(params: { + createCommandSurface: CreateDaemonCommandSurface; req: DaemonRequest; sessionName: string; logPath: string; @@ -176,7 +178,7 @@ function createSnapshotRuntime(params: { return ref ?? sessionStore.publish(sessionName, nextSession); }, }); - const runtime = createCommandSurfaceAgentDevice({ + const runtime = params.createCommandSurface({ backend: createDaemonSnapshotBackend({ req, logPath, diff --git a/src/daemon/snapshot-runtime-binding.ts b/src/daemon/snapshot-runtime-binding.ts index 4af8c430af..518288fe83 100644 --- a/src/daemon/snapshot-runtime-binding.ts +++ b/src/daemon/snapshot-runtime-binding.ts @@ -1,5 +1,6 @@ import type { DaemonPlatformServices } from './platform-services.ts'; import type { RuntimeOperationFact } from '@agent-device/contracts/platform-runtime'; +import type { CreateDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; import { type SelectorCaptureRuntimePlan, type SnapshotRuntimePlan, @@ -42,6 +43,8 @@ import { AppError } from '@agent-device/kernel/errors'; import { isHandheldAppleDevice } from '@agent-device/kernel/device'; export type SnapshotRuntimeRouteParams = { + /** Builds the command surface this request's snapshot runtime executes through. */ + createCommandSurface: CreateDaemonCommandSurface; req: DaemonRequest; sessionName: string; sessionRef?: SessionRef; diff --git a/src/daemon/snapshot-runtime.ts b/src/daemon/snapshot-runtime.ts index fdf7a2f5a8..58f8f2c02f 100644 --- a/src/daemon/snapshot-runtime.ts +++ b/src/daemon/snapshot-runtime.ts @@ -43,6 +43,7 @@ export async function dispatchSnapshotViaRuntime( ); const session = getSession(); const fallbackScreenshot = await captureSparseFallbackScreenshot({ + createCommandSurface: params.createCommandSurface, req: request, session, sessionName: resolvedSessionName, diff --git a/src/daemon/sparse-fallback-screenshot.ts b/src/daemon/sparse-fallback-screenshot.ts index b9d34d365a..a57f53a6a5 100644 --- a/src/daemon/sparse-fallback-screenshot.ts +++ b/src/daemon/sparse-fallback-screenshot.ts @@ -1,4 +1,5 @@ import type { DeviceRotation } from '@agent-device/contracts/device'; +import type { CreateDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; import type { SnapshotQualityVerdict } from '@agent-device/kernel/snapshot'; import { isSparseSnapshotQualityVerdict } from '@agent-device/capture-kit/snapshot-quality-verdict'; import { contextFromFlags } from './context.ts'; @@ -35,6 +36,7 @@ export type SparseFallbackScreenshot = { */ export async function captureSparseFallbackScreenshot( params: { + createCommandSurface: CreateDaemonCommandSurface; req: DaemonRequest; session: SessionState | undefined; sessionName: string; @@ -64,6 +66,7 @@ export async function captureSparseFallbackScreenshot( async function captureFallbackScreenshot( params: { + createCommandSurface: CreateDaemonCommandSurface; req: DaemonRequest; session: SessionState; sessionName: string; @@ -83,6 +86,7 @@ async function captureFallbackScreenshot( // warning still carries the manual remedy. if (!capture.ok) return undefined; const data = await captureScreenshotArtifact({ + createCommandSurface: params.createCommandSurface, session, sessionName: params.sessionName, // No caller-supplied destination: the screenshot artifact adapter mints a temp diff --git a/test/integration/provider-scenarios/harness.ts b/test/integration/provider-scenarios/harness.ts index 36fd7fe004..0ec6bbaaf7 100644 --- a/test/integration/provider-scenarios/harness.ts +++ b/test/integration/provider-scenarios/harness.ts @@ -52,6 +52,7 @@ import { getInteractor } from '../../../src/core/interactors.ts'; import { unavailableDeviceRuntimeGateway } from '../../../src/daemon/__tests__/test-device-runtime-gateway.ts'; import { openWebSessionNames } from '../../../src/daemon/web-session-names.ts'; +import { testCreateCommandSurface } from '../../../src/daemon/__tests__/command-surface-fixture.ts'; // Match daemon composition (src/daemon/server/daemon-runtime.ts): the daemon decides on provider // ownership through its own admission seam, which root composition installs. @@ -167,6 +168,7 @@ export async function createProviderScenarioHarness( }) : unavailableDeviceRuntimeGateway); const requestHandler = createRequestHandler({ + createCommandSurface: testCreateCommandSurface, logPath: path.join(os.tmpdir(), 'agent-device-provider-scenario-daemon.log'), token: PROVIDER_SCENARIO_TOKEN, sessionStore, From 8f5818e22f6c0e25d1cd145307785590bfe673e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sun, 11 Oct 2026 08:47:59 +0200 Subject: [PATCH 2/4] chore(gates): snapshot the daemon-command-surface contracts export --- scripts/layering/contracts-exports.snapshot.json | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/layering/contracts-exports.snapshot.json b/scripts/layering/contracts-exports.snapshot.json index 00486cfb44..7fb3fb7798 100644 --- a/scripts/layering/contracts-exports.snapshot.json +++ b/scripts/layering/contracts-exports.snapshot.json @@ -39,6 +39,7 @@ "@agent-device/contracts/command", "@agent-device/contracts/command-platform-execution", "@agent-device/contracts/command-policy", + "@agent-device/contracts/daemon-command-surface", "@agent-device/contracts/daemon-http", "@agent-device/contracts/deploy-result-target", "@agent-device/contracts/device", From 62dea9156a2d2361157206e105d5a285a990d224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sun, 11 Oct 2026 09:03:24 +0200 Subject: [PATCH 3/4] refactor(daemon): load the command surface lazily and check the port contravariantly - src/daemon.ts injects loadCommandSurface behind await import so the command surface stays out of the daemon entry's eager closure (470 modules, at the merge-base; the static import reached 550). startDaemonRuntime awaits the loader once and threads the sync factory unchanged. - DaemonCommandSurface{Capture,Selectors,Interactions} members become function-typed properties so strictFunctionTypes checks them contravariantly. The check found the port's DaemonGetOptions.target was InteractionTarget while the implementation takes ElementTarget; the port narrows to ElementTarget, which matches the one call site (ref | selector). --- .../contracts/src/daemon-command-surface.ts | 67 ++++++++++++------- src/__tests__/daemon-entrypoint.test.ts | 10 +-- src/daemon.ts | 8 ++- .../__tests__/interaction-settle.test.ts | 35 ++++++---- src/daemon/selector-runtime-backend.test.ts | 11 +-- .../server/daemon-runtime-idle-reap.test.ts | 6 +- ...mon-runtime-interactor-composition.test.ts | 6 +- .../daemon-runtime-lifecycle-shutdown.test.ts | 12 ++-- .../daemon-runtime-recording-reaper.test.ts | 2 +- .../daemon-runtime-xctest-device-set.test.ts | 2 +- src/daemon/server/daemon-runtime.ts | 15 +++-- 11 files changed, 107 insertions(+), 67 deletions(-) diff --git a/packages/contracts/src/daemon-command-surface.ts b/packages/contracts/src/daemon-command-surface.ts index 8f25f2b8f0..7e92b5bd88 100644 --- a/packages/contracts/src/daemon-command-surface.ts +++ b/packages/contracts/src/daemon-command-surface.ts @@ -19,6 +19,7 @@ import type { } from './gesture-plan-types.ts'; import type { FindLocator } from './snapshot-types.ts'; import type { + ElementTarget, FillCommandResult, FindReadResult, HoverCommandResult, @@ -110,7 +111,9 @@ export type DaemonGetOptions = CommandContext & DaemonSelectorSnapshotOptions & Readonly<{ property: 'text' | 'attrs'; - target: InteractionTarget; + // `get` reads an element, never a coordinate: the implementation's options take an + // ElementTarget, and contravariant checking only holds while the port agrees. + target: ElementTarget; /** ADR 0012 step 4: replay-only post-resolution guard. */ expectedResolvedTarget?: ReplayTargetGuardDenotation; }>; @@ -295,50 +298,57 @@ export type DaemonSettleObservationOptions = CommandContext & baseline: SurfaceScopedNodes; }>; -/** The three captures the daemon runs. */ +/** + * The three captures the daemon runs. Members are function-typed properties, not method + * shorthand: `strictFunctionTypes` checks properties contravariantly, so an implementation + * whose options require a field this port does not pass fails the build instead of compiling + * through bivariant method checking. + */ export type DaemonCommandSurfaceCapture = Readonly<{ - screenshot(options: DaemonScreenshotCommandOptions): Promise; - snapshot(options: DaemonSnapshotOptions): Promise; - diffSnapshot(options: DaemonSnapshotOptions): Promise; + screenshot: (options: DaemonScreenshotCommandOptions) => Promise; + snapshot: (options: DaemonSnapshotOptions) => Promise; + diffSnapshot: (options: DaemonSnapshotOptions) => Promise; }>; -/** The five selector reads the daemon's selector and wait routes serve. */ +/** The five selector reads the daemon's selector and wait routes serve. Function-typed + * properties for the same contravariance reason as {@link DaemonCommandSurfaceCapture}. */ export type DaemonCommandSurfaceSelectors = Readonly<{ - find(options: DaemonFindOptions): Promise; - get(options: DaemonGetOptions): Promise; - is(options: DaemonIsOptions): Promise; - wait(options: DaemonWaitOptions): Promise; - waitForText( + find: (options: DaemonFindOptions) => Promise; + get: (options: DaemonGetOptions) => Promise; + is: (options: DaemonIsOptions) => Promise; + wait: (options: DaemonWaitOptions) => Promise; + waitForText: ( text: string, options?: Omit, - ): Promise; + ) => Promise; }>; -/** The seven actions the daemon's touch, gesture and settle routes serve. */ +/** The seven actions the daemon's touch, gesture and settle routes serve. Function-typed + * properties for the same contravariance reason as {@link DaemonCommandSurfaceCapture}. */ export type DaemonCommandSurfaceInteractions = Readonly<{ - click( + click: ( target: InteractionTarget, options?: Omit, - ): Promise; - press( + ) => Promise; + press: ( target: InteractionTarget, options?: Omit, - ): Promise; - fill( + ) => Promise; + fill: ( target: InteractionTarget, text: string, options?: Omit, - ): Promise; - longPress( + ) => Promise; + longPress: ( target: InteractionTarget, options?: Omit, - ): Promise; - hover( + ) => Promise; + hover: ( target: InteractionTarget, options?: Omit, - ): Promise; - gesture(options: DaemonGestureOptions): Promise; - settleObservation(options: DaemonSettleObservationOptions): Promise; + ) => Promise; + gesture: (options: DaemonGestureOptions) => Promise; + settleObservation: (options: DaemonSettleObservationOptions) => Promise; }>; /** What a daemon route may call, and nothing else. */ @@ -355,3 +365,10 @@ export type DaemonCommandSurface = Readonly<{ * supplies `createCommandSurfaceAgentDevice`; a test supplies a fake through the same seam. */ export type CreateDaemonCommandSurface = (config: AgentDeviceRuntimeConfig) => DaemonCommandSurface; + +/** + * How the composition root hands the factory to the daemon: the daemon awaits this once at + * startup, so the entry executable can keep the surface behind `await import` and out of its + * eager closure (ADR 0019). + */ +export type LoadDaemonCommandSurface = () => Promise; diff --git a/src/__tests__/daemon-entrypoint.test.ts b/src/__tests__/daemon-entrypoint.test.ts index fe078fcdb6..6f38f41279 100644 --- a/src/__tests__/daemon-entrypoint.test.ts +++ b/src/__tests__/daemon-entrypoint.test.ts @@ -88,7 +88,7 @@ test('daemon runtime starts HTTP transport in-process and shuts down cleanly', a try { const runtime = await startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -148,7 +148,7 @@ test('daemon runtime publishes dual transport metadata', async () => { try { const runtime = await startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -199,7 +199,7 @@ test('daemon rejects unowned cloud artifacts over RPC', async () => { try { runtime = await startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -330,7 +330,7 @@ test('daemon runtime records startup device-claim reconciliation in daemon.log', ); const runtime = await startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -456,7 +456,7 @@ test('startup sweep settles a foreign dead owner without touching a live same-na ); runtime = await startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: daemonStateDir, diff --git a/src/daemon.ts b/src/daemon.ts index 6a432496ab..1ab1ad0958 100644 --- a/src/daemon.ts +++ b/src/daemon.ts @@ -1,10 +1,12 @@ -import { createCommandSurfaceAgentDevice } from './command-runtime/runtime-command-surface.ts'; import { startDaemonRuntime } from './daemon/server/daemon-runtime.ts'; import { asAppError } from '@agent-device/kernel/errors'; void startDaemonRuntime({ - // This executable is the composition root that builds the daemon's command surface. - createCommandSurface: createCommandSurfaceAgentDevice, + // This executable is the composition root that builds the daemon's command surface. The + // loader stays behind `await import` (ADR 0019): the whole surface would otherwise join + // src/daemon.ts's eager closure and undo the lazy generic-dispatch split. + loadCommandSurface: async () => + (await import('./command-runtime/runtime-command-surface.ts')).createCommandSurfaceAgentDevice, }).catch((error) => { const appErr = asAppError(error); process.stderr.write(`Daemon error: ${appErr.message}\n`); diff --git a/src/daemon/interaction/internal/__tests__/interaction-settle.test.ts b/src/daemon/interaction/internal/__tests__/interaction-settle.test.ts index 6e05343860..18eddbcf67 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-settle.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-settle.test.ts @@ -27,6 +27,7 @@ import { mockTapPoint, resetGetRuntimeFixture, } from '../../../__tests__/interaction-get-runtime-fixture.ts'; +import { testCreateCommandSurface } from '../../../__tests__/command-surface-fixture.ts'; // #1101 --settle daemon response shape: the settle payload (diff + settled + // refsGeneration) rides the wire response through the shared builder, and a @@ -102,7 +103,7 @@ function seedSession(sessionName: string, sessionStore: ReturnType { +test('interaction runtime inherits the registered daemon request signal', async () => { const sessionStore = makeSessionStore(); const sessionName = 'request-signal'; const requestId = 'request-signal-id'; @@ -111,8 +112,9 @@ test('interaction runtime inherits the registered daemon request signal', () => expect(registration).toBeDefined(); try { - const runtime = createInteractionRuntime({ - platformServices: daemonPlatformServicesFixture(), + const runtime = await createInteractionRuntime({ + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -216,7 +218,8 @@ test('press --settle responds with the settled diff, refsGeneration, and activat mockCommandDispatch({ snapshots: [BEFORE_NODES, AFTER_NODES, AFTER_NODES, AFTER_NODES] }); const response = await handleInteractionCommands({ - req: { + + createCommandSurface: testCreateCommandSurface,req: { token: 't', session: sessionName, command: 'press', @@ -278,7 +281,8 @@ test('held touch settle publishes refs into the current record of its scoped lif }; }); const running = handleInteractionCommands({ - req: { + + createCommandSurface: testCreateCommandSurface,req: { token: 't', session: address, command: 'press', @@ -352,7 +356,8 @@ test('press --settle on a removals-only diff attaches the unchanged interactive }); const response = await handleInteractionCommands({ - req: { + + createCommandSurface: testCreateCommandSurface,req: { token: 't', session: sessionName, command: 'press', @@ -388,7 +393,8 @@ test('press --settle rejects an expired-frame ref before dispatch or observation ); const response = await handleInteractionCommands({ - req: { + + createCommandSurface: testCreateCommandSurface,req: { token: 't', session: sessionName, command: 'press', @@ -429,7 +435,8 @@ test('a settle observation without a diff leaves ref staleness untouched', async }); const response = await handleInteractionCommands({ - req: { + + createCommandSurface: testCreateCommandSurface,req: { token: 't', session: sessionName, command: 'press', @@ -494,7 +501,8 @@ test('a stalled settle capture receives its deadline signal and leaves the inter const startedAt = Date.now(); const response = await handleInteractionCommands({ - req: { + + createCommandSurface: testCreateCommandSurface,req: { token: 't', session: sessionName, command: 'press', @@ -524,7 +532,8 @@ test('bare timeout without --settle stays compatible', async () => { mockCommandDispatch({ snapshots: [BEFORE_NODES] }); const compatible = await handleInteractionCommands({ - req: { + + createCommandSurface: testCreateCommandSurface,req: { token: 't', session: sessionName, command: 'press', @@ -549,7 +558,8 @@ test('settle-specific tuning flags without --settle are rejected', async () => { mockCommandDispatch({ snapshots: [BEFORE_NODES] }); const response = await handleInteractionCommands({ - req: { + + createCommandSurface: testCreateCommandSurface,req: { token: 't', session: sessionName, command: 'press', @@ -574,7 +584,8 @@ test('fill @ref --settle carries the settle payload on the ref wire shape', asyn mockCommandDispatch({ snapshots: [AFTER_NODES, AFTER_NODES] }); const response = await handleInteractionCommands({ - req: { + + createCommandSurface: testCreateCommandSurface,req: { token: 't', session: sessionName, command: 'fill', diff --git a/src/daemon/selector-runtime-backend.test.ts b/src/daemon/selector-runtime-backend.test.ts index d9d0d4e763..352089d0d9 100644 --- a/src/daemon/selector-runtime-backend.test.ts +++ b/src/daemon/selector-runtime-backend.test.ts @@ -10,6 +10,7 @@ import { SessionStore } from './session-store.ts'; import type { SessionState } from './session-state.ts'; import { mkdtempForTestSync } from '../__tests__/test-utils/tmp-dir.ts'; import { makeSnapshotState } from '@agent-device/selectors/snapshot-geometry-fixtures'; +import { testCreateCommandSurface } from './__tests__/command-surface-fixture.ts'; const device: SessionState['device'] = { platform: 'apple', @@ -52,8 +53,9 @@ test('wait text passes its poll deadline signal to the Apple runner fast path', ); }), ); - const runtime = createSelectorRuntimeForDevice({ - platformServices: daemonPlatformServicesFixture(), + const runtime = await createSelectorRuntimeForDevice({ + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), ref: sessionStore.lookup(sessionName), req: { token: 't', @@ -114,8 +116,9 @@ test('daemon wait stable pins private-ax on emitted snapshot runner requests', a reasonCode: 'requested-backend', }, })); - const runtime = createSelectorRuntimeForDevice({ - platformServices: daemonPlatformServicesFixture(), + const runtime = await createSelectorRuntimeForDevice({ + + createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), ref: sessionStore.lookup(sessionName), req: { token: 't', diff --git a/src/daemon/server/daemon-runtime-idle-reap.test.ts b/src/daemon/server/daemon-runtime-idle-reap.test.ts index e361cdd698..99e5c65b6e 100644 --- a/src/daemon/server/daemon-runtime-idle-reap.test.ts +++ b/src/daemon/server/daemon-runtime-idle-reap.test.ts @@ -38,7 +38,7 @@ test('daemon runtime self-reaps after the idle window when nothing ever uses it' try { const runtime = await startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -78,7 +78,7 @@ test('daemon runtime never self-reaps when AGENT_DEVICE_DAEMON_IDLE_TIMEOUT_MS i try { const runtime = await startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -116,7 +116,7 @@ test('only a retained lease keeps the daemon runtime from self-reaping', async ( try { const runtime = await startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, diff --git a/src/daemon/server/daemon-runtime-interactor-composition.test.ts b/src/daemon/server/daemon-runtime-interactor-composition.test.ts index 6a1cf2180d..02e6121ac1 100644 --- a/src/daemon/server/daemon-runtime-interactor-composition.test.ts +++ b/src/daemon/server/daemon-runtime-interactor-composition.test.ts @@ -61,7 +61,7 @@ test('daemon startup composes the interactor resolution the daemon resolves thro const providerInteractor = { snapshot: async () => ({ nodes: [] }) } as unknown as Interactor; try { const runtime = await startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -96,7 +96,7 @@ test('daemon startup compares lease credentials with its own startup environment const stateDir = mkdtempForTestSync('agent-device-daemon-credential-composition-'); const daemonEnv = { BROWSERSTACK_USERNAME: 'user', BROWSERSTACK_ACCESS_KEY: 'key-1' }; const runtime = await startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { ...process.env, ...daemonEnv, @@ -163,7 +163,7 @@ test('a daemon attempt losing the lock shuts down every constructed provider', a }); const exit = vi.fn(); const runtime = await startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { AGENT_DEVICE_STATE_DIR: stateDir }, exit, registerProcessHandlers: false, diff --git a/src/daemon/server/daemon-runtime-lifecycle-shutdown.test.ts b/src/daemon/server/daemon-runtime-lifecycle-shutdown.test.ts index 111af1a5b9..6a79f161fd 100644 --- a/src/daemon/server/daemon-runtime-lifecycle-shutdown.test.ts +++ b/src/daemon/server/daemon-runtime-lifecycle-shutdown.test.ts @@ -155,7 +155,7 @@ test('daemon shutdown detaches before session teardown and force-finalizes only const startupErrors: string[] = []; try { const runtime = await startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -184,7 +184,7 @@ test('a SIGTERM shutdown gives the handoff a diagnostics scope to write its reas const stateDir = mkdtempForTestSync('agent-device-daemon-detach-diagnostics-'); try { const runtime = await startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -213,7 +213,7 @@ const claimStores = isolatedDeviceClaimStores('daemon-drain-admission-'); test('shutdown includes drain publications, releases their claims and refuses post-snapshot publication', async () => { const { stateDir } = claimStores(); const runtime = await startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -268,7 +268,7 @@ test('shutdown includes drain publications, releases their claims and refuses po test('shutdown joins dispatch completion after force-closing the client before taking its snapshot', async () => { const { stateDir } = claimStores(); const runtime = await startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -407,7 +407,7 @@ test.each([false, true])( test('daemon shutdown releases a session lease whose session teardown rejects', async () => { const stateDir = mkdtempForTestSync('agent-device-daemon-session-lease-shutdown-'); const runtime = await startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -442,7 +442,7 @@ test('daemon shutdown releases a retainOnClose lease that no session holds', asy const stateDir = mkdtempForTestSync('agent-device-daemon-retained-lease-shutdown-'); try { const runtime = await startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, diff --git a/src/daemon/server/daemon-runtime-recording-reaper.test.ts b/src/daemon/server/daemon-runtime-recording-reaper.test.ts index c64fde7db4..a5560317f4 100644 --- a/src/daemon/server/daemon-runtime-recording-reaper.test.ts +++ b/src/daemon/server/daemon-runtime-recording-reaper.test.ts @@ -53,7 +53,7 @@ test('daemon startup reaps orphaned simctl recorders with a graceful finalize wi const stateDir = mkdtempForTestSync('agent-device-daemon-recording-reaper-'); try { const runtime = await startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, diff --git a/src/daemon/server/daemon-runtime-xctest-device-set.test.ts b/src/daemon/server/daemon-runtime-xctest-device-set.test.ts index 22cbc09d45..6d0e4d644f 100644 --- a/src/daemon/server/daemon-runtime-xctest-device-set.test.ts +++ b/src/daemon/server/daemon-runtime-xctest-device-set.test.ts @@ -75,7 +75,7 @@ function loggedEvents(logPath: string): Array<{ phase: string; data?: Record startDaemonRuntime({ - createCommandSurface: testCreateCommandSurface, + loadCommandSurface: async () => testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, diff --git a/src/daemon/server/daemon-runtime.ts b/src/daemon/server/daemon-runtime.ts index 6bde5986a1..4529ec02e0 100644 --- a/src/daemon/server/daemon-runtime.ts +++ b/src/daemon/server/daemon-runtime.ts @@ -18,7 +18,7 @@ import { loadDaemonPolicy, type DaemonPolicy, } from '@agent-device/daemon-contracts/daemon-policy-file'; -import type { CreateDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; +import type { LoadDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; import { getInteractor } from '../../core/interactors.ts'; import { installInteractorResolution } from '../interactor-resolution.ts'; import { @@ -212,8 +212,12 @@ export type DaemonRuntimeOptions = { stderr?: WritableOutput; exit?: (code: number) => void; registerProcessHandlers?: boolean; - /** The command-surface factory the request handler builds per-request runtimes from. */ - createCommandSurface: CreateDaemonCommandSurface; + /** + * Loads the command-surface factory the request handler builds per-request runtimes from. + * Required, and awaited once at startup: the composition root keeps the surface behind + * `await import` so it never joins `src/daemon.ts`'s eager closure (ADR 0019). + */ + loadCommandSurface: LoadDaemonCommandSurface; }; export type DaemonRuntimeController = { @@ -331,6 +335,9 @@ export async function startDaemonRuntime( const daemonPaths = resolveDaemonPaths(env.AGENT_DEVICE_STATE_DIR); const { baseDir, infoPath, logPath, sessionsDir } = daemonPaths; const daemonServerMode = resolveDaemonServerMode(env.AGENT_DEVICE_DAEMON_SERVER_MODE); + // The composition root's loader resolves once here; the request handler keeps threading the + // sync factory it returns, so the `await import` never enters this module's import time. + const createCommandSurface = await options.loadCommandSurface(); const retainArtifacts = isEnvTruthy(env.AGENT_DEVICE_RETAIN_ARTIFACTS); // ADR 0029: a policy that cannot be read or validated stops startup; the daemon never runs // with a weaker policy than its operator named. @@ -454,7 +461,7 @@ export async function startDaemonRuntime( providerDeviceRuntimeScope: providerRuntimeProviders.providerDeviceRuntimeScope, trackDownloadableArtifact, daemonPolicy, - createCommandSurface: options.createCommandSurface, + createCommandSurface, }); let stopMetadataLossWatch: () => void = () => {}; From 7c4f6b7165bfc74202276f43c5ac36e4da6204fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sun, 11 Oct 2026 09:15:50 +0200 Subject: [PATCH 4/4] refactor(daemon): load the command surface on first request use, not at startup CreateDaemonCommandSurface becomes an async factory: the composition root's lambda keeps the surface behind await import, and the four construction sites await the port, so a daemon that never serves a surface command never evaluates it. startDaemonRuntime's startup await and the LoadDaemonCommandSurface seam go away; the test fixture wraps the real factory in an async lambda and the test bags keep the createCommandSurface field name. --- .codemod-tmp.mjs | 109 ------------------ .../contracts/src/daemon-command-surface.ts | 14 +-- src/__tests__/daemon-entrypoint.test.ts | 10 +- ...ssion-test-reporter-values-maestro.test.ts | 4 +- .../session-test-reporter-values.test.ts | 4 +- src/daemon.ts | 10 +- .../__tests__/command-surface-fixture.ts | 9 +- src/daemon/__tests__/generic-settle.test.ts | 8 +- src/daemon/__tests__/is-runtime.test.ts | 52 ++++----- ...n-replay-maestro-remote-evalscript.test.ts | 4 +- .../session-test-fail-fast.test.ts | 4 +- .../replay-suite/session-test-runner.test.ts | 36 +++--- .../session-test-suite-infrastructure.test.ts | 8 +- .../replay-suite/session-test-suite.test.ts | 68 +++++------ .../__tests__/request-handler-catalog.test.ts | 4 +- ...quest-handler-chain-provider-scope.test.ts | 8 +- .../__tests__/screenshot-runtime.test.ts | 48 ++++---- .../selector-capture-binding.test.ts | 8 +- ...elector-runtime-ambiguity-issuance.test.ts | 24 ++-- src/daemon/__tests__/selector-runtime.test.ts | 4 +- .../snapshot-command-runtime.test.ts | 16 +-- .../__tests__/snapshot-diff-runtime.test.ts | 12 +- .../snapshot-runtime-binding.test.ts | 4 +- .../sparse-fallback-screenshot.test.ts | 4 +- src/daemon/__tests__/wait-runtime.test.ts | 12 +- src/daemon/generic-settle.ts | 8 +- .../__tests__/session-appstate-input.test.ts | 40 +++---- .../__tests__/session-boot-shutdown.test.ts | 64 +++++----- .../__tests__/session-command-replay.test.ts | 28 ++--- .../__tests__/session-device-claims.test.ts | 40 +++---- .../session-install-source-ref-frame.test.ts | 4 +- .../__tests__/session-push-ref-frame.test.ts | 4 +- .../handlers/__tests__/session-push.test.ts | 24 ++-- .../__tests__/session-reinstall.test.ts | 28 ++--- .../__tests__/session-relaunch-guards.test.ts | 32 ++--- .../__tests__/session-runtime-command.test.ts | 36 +++--- .../session-selector-dispatch.test.ts | 24 ++-- .../session-state-boot-warmup.test.ts | 4 +- .../__tests__/session-trigger.test.ts | 4 +- .../snapshot-handler-freshness.test.ts | 32 ++--- .../__tests__/snapshot-handler.test.ts | 52 ++++----- .../snapshot-settings-handler.test.ts | 96 +++++++-------- .../handlers/snapshot-runtime-route.test.ts | 8 +- src/daemon/interaction/index.ts | 4 +- .../interaction-dispatch-disclosure.test.ts | 24 ++-- .../interaction-gesture-drag.test.ts | 4 +- .../__tests__/interaction-settle.test.ts | 40 +++---- .../internal/interaction-gesture.ts | 13 ++- .../internal/interaction-runtime.ts | 4 +- .../internal/interaction-touch-press.ts | 2 +- .../internal/interaction-touch-runtime.ts | 4 +- src/daemon/screenshot-runtime.ts | 2 +- src/daemon/selector-runtime-backend.test.ts | 8 +- src/daemon/selector-runtime-backend.ts | 8 +- .../server/daemon-runtime-idle-reap.test.ts | 6 +- ...mon-runtime-interactor-composition.test.ts | 6 +- .../daemon-runtime-lifecycle-shutdown.test.ts | 12 +- .../daemon-runtime-metadata-ownership.test.ts | 4 +- .../daemon-runtime-recording-reaper.test.ts | 2 +- .../daemon-runtime-xctest-device-set.test.ts | 2 +- src/daemon/server/daemon-runtime.ts | 15 +-- .../__tests__/application.test.ts | 4 +- ...on-capabilities-install-projection.test.ts | 4 +- .../__tests__/session-capabilities.test.ts | 36 +++--- ...session-close-cwd-scoped-resources.test.ts | 4 +- .../session-close-error-precedence.test.ts | 20 ++-- .../session-close-lifecycle-runtime.test.ts | 4 +- .../session-close-resource-cleanup.test.ts | 16 +-- .../session-close-save-script.test.ts | 12 +- .../session-close-shutdown-platform.test.ts | 28 ++--- .../session-devices-batch-runtime.test.ts | 4 +- .../session-open-execution-runtime.test.ts | 36 +++--- .../__tests__/session-open-existing.test.ts | 40 +++---- .../__tests__/session-open-foreground.test.ts | 20 ++-- .../__tests__/session-open-runtime.test.ts | 28 ++--- .../session-teardown-resources.test.ts | 8 +- .../__tests__/application.test.ts | 4 +- .../internal/__tests__/session-logs.test.ts | 4 +- .../session-network-runtime-parity.test.ts | 12 +- .../__tests__/session-network.test.ts | 8 +- src/daemon/snapshot-command-runtime.ts | 8 +- src/daemon/wait-runtime.ts | 2 +- 82 files changed, 680 insertions(+), 794 deletions(-) delete mode 100644 .codemod-tmp.mjs diff --git a/.codemod-tmp.mjs b/.codemod-tmp.mjs deleted file mode 100644 index 6825070ffe..0000000000 --- a/.codemod-tmp.mjs +++ /dev/null @@ -1,109 +0,0 @@ -// Codemod: insert `createCommandSurface: testCreateCommandSurface,` into object literals -// reported by tsc as missing the required port, and add the fixture import. -import fs from 'node:fs'; -import path from 'node:path'; -import { createRequire } from 'node:module'; -const ts = createRequire(import.meta.url)( - new URL('./node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/typescript.js', `file://${process.cwd()}/`).pathname, -); - -const ROOT = process.cwd(); -const FIXTURE = 'src/daemon/__tests__/command-surface-fixture.ts'; -const PROP = 'createCommandSurface'; -const VALUE = 'testCreateCommandSurface'; - -const errLines = fs - .readFileSync(process.argv[2], 'utf8') - .split('\n') - .filter((l) => /error TS(2741|2345)/.test(l) || /error TS2322/.test(l)); - -const byFile = new Map(); -for (const line of errLines) { - const m = line.match(/^(.+?)\((\d+),(\d+)\): error TS(\d+)/); - if (!m) continue; - const [, file, row, col, code] = m; - if (!/(^|\/)(__tests__|test)\//.test(file) && !file.includes('.test.ts')) continue; - if (!byFile.has(file)) byFile.set(file, []); - byFile.get(file).push({ row: +row, col: +col, code: +code }); -} - -let insertions = 0; -const changedFiles = new Set(); -const notes = []; - -for (const [relFile, errs] of byFile) { - const abs = path.join(ROOT, relFile); - if (!fs.existsSync(abs)) { - notes.push(`MISSING ${relFile}`); - continue; - } - const text = fs.readFileSync(abs, 'utf8'); - const src = ts.createSourceFile(abs, text, ts.ScriptTarget.Latest, true); - const lineStarts = ts.computeLineStarts(text); - const literals = []; - (function walk(node) { - if (ts.isObjectLiteralExpression(node)) literals.push(node); - node.forEachChild(walk); - })(src); - - const starts = new Set(); - for (const e of errs) { - const pos = lineStarts[e.row - 1] + e.col - 1; - let cands = literals - .filter((lit) => lit.getStart(src) <= pos && lit.getEnd() >= pos) - .sort((a, b) => b.getStart(src) - a.getStart(src)); - if (cands.length === 0) { - const lineEnd = lineStarts[e.row] ?? text.length; - cands = literals - .filter((lit) => lit.getStart(src) >= pos && lit.getStart(src) < lineEnd) - .sort((a, b) => a.getStart(src) - b.getStart(src)); - } - if (cands.length === 0) { - notes.push(`NO-LITERAL ${relFile} ${e.row} ${e.col} TS${e.code}`); - continue; - } - const lit = cands[0]; - if (lit.properties.some((p) => p.name && p.name.getText(src) === PROP)) continue; - starts.add(lit.getStart(src)); - } - if (starts.size === 0) continue; - - let out = text; - const edits = []; - for (const start of starts) { - const lit = literals.find((l) => l.getStart(src) === start); - const first = lit.properties[0]; - let indent = ' '; - if (first) { - const ls = text.lastIndexOf('\n', first.getStart(src)) + 1; - const m = text.slice(ls, first.getStart(src)).match(/[ \t]*$/); - if (m && m[0]) indent = m[0]; - } - const insertion = `\n${indent}${PROP}: ${VALUE},`; - const at = first ? first.getStart(src) : lit.getStart(src) + 1; - edits.push({ at, insertion }); - insertions += 1; - } - edits.sort((a, b) => b.at - a.at); - for (const e of edits) out = out.slice(0, e.at) + e.insertion + out.slice(e.at); - - if (!new RegExp(`import\\s*\\{[^}]*\\b${VALUE}\\b[^}]*\\}`).test(out)) { - const rel = path - .relative(path.dirname(abs), path.join(ROOT, FIXTURE)) - .replace(/\\/g, '/'); - const spec = rel.startsWith('.') ? rel : './' + rel; - const imp = `import { ${VALUE} } from '${spec}';`; - const tmp = ts.createSourceFile(abs, out, ts.ScriptTarget.Latest, true); - let lastImportEnd = 0; - for (const st of tmp.statements) { - if (ts.isImportDeclaration(st)) lastImportEnd = st.getEnd(); - else break; - } - if (lastImportEnd > 0) out = out.slice(0, lastImportEnd) + '\n' + imp + out.slice(lastImportEnd); - else out = imp + '\n' + out; - } - fs.writeFileSync(abs, out); - changedFiles.add(relFile); -} -console.log('FILES', changedFiles.size, 'INSERTIONS', insertions); -for (const n of notes) console.log(n); diff --git a/packages/contracts/src/daemon-command-surface.ts b/packages/contracts/src/daemon-command-surface.ts index 7e92b5bd88..31f25c8d3e 100644 --- a/packages/contracts/src/daemon-command-surface.ts +++ b/packages/contracts/src/daemon-command-surface.ts @@ -362,13 +362,9 @@ export type DaemonCommandSurface = Readonly<{ /** * The injection seam: one command surface per request-bound runtime config. The composition root - * supplies `createCommandSurfaceAgentDevice`; a test supplies a fake through the same seam. + * supplies `createCommandSurfaceAgentDevice` behind an async lambda so the surface loads on the + * first request that needs it; a test supplies a fake through the same seam. */ -export type CreateDaemonCommandSurface = (config: AgentDeviceRuntimeConfig) => DaemonCommandSurface; - -/** - * How the composition root hands the factory to the daemon: the daemon awaits this once at - * startup, so the entry executable can keep the surface behind `await import` and out of its - * eager closure (ADR 0019). - */ -export type LoadDaemonCommandSurface = () => Promise; +export type CreateDaemonCommandSurface = ( + config: AgentDeviceRuntimeConfig, +) => Promise; diff --git a/src/__tests__/daemon-entrypoint.test.ts b/src/__tests__/daemon-entrypoint.test.ts index 6f38f41279..fe078fcdb6 100644 --- a/src/__tests__/daemon-entrypoint.test.ts +++ b/src/__tests__/daemon-entrypoint.test.ts @@ -88,7 +88,7 @@ test('daemon runtime starts HTTP transport in-process and shuts down cleanly', a try { const runtime = await startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -148,7 +148,7 @@ test('daemon runtime publishes dual transport metadata', async () => { try { const runtime = await startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -199,7 +199,7 @@ test('daemon rejects unowned cloud artifacts over RPC', async () => { try { runtime = await startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -330,7 +330,7 @@ test('daemon runtime records startup device-claim reconciliation in daemon.log', ); const runtime = await startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -456,7 +456,7 @@ test('startup sweep settles a foreign dead owner without touching a live same-na ); runtime = await startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: daemonStateDir, diff --git a/src/cli/replay-test/__tests__/session-test-reporter-values-maestro.test.ts b/src/cli/replay-test/__tests__/session-test-reporter-values-maestro.test.ts index e9ba4d57cc..6c8cf95598 100644 --- a/src/cli/replay-test/__tests__/session-test-reporter-values-maestro.test.ts +++ b/src/cli/replay-test/__tests__/session-test-reporter-values-maestro.test.ts @@ -114,8 +114,8 @@ async function runMaestroSuiteThroughReporter(params: { (event) => runReplayTestReporterProgress(reporters, event, reporterContext), async () => await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/cli/replay-test/__tests__/session-test-reporter-values.test.ts b/src/cli/replay-test/__tests__/session-test-reporter-values.test.ts index 387d2ef11a..babe8c1e1e 100644 --- a/src/cli/replay-test/__tests__/session-test-reporter-values.test.ts +++ b/src/cli/replay-test/__tests__/session-test-reporter-values.test.ts @@ -104,8 +104,8 @@ async function runSuiteThroughReporter(params: { (event) => runReplayTestReporterProgress(reporters, event, reporterContext), async () => await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon.ts b/src/daemon.ts index 1ab1ad0958..4a71442e3f 100644 --- a/src/daemon.ts +++ b/src/daemon.ts @@ -2,11 +2,11 @@ import { startDaemonRuntime } from './daemon/server/daemon-runtime.ts'; import { asAppError } from '@agent-device/kernel/errors'; void startDaemonRuntime({ - // This executable is the composition root that builds the daemon's command surface. The - // loader stays behind `await import` (ADR 0019): the whole surface would otherwise join - // src/daemon.ts's eager closure and undo the lazy generic-dispatch split. - loadCommandSurface: async () => - (await import('./command-runtime/runtime-command-surface.ts')).createCommandSurfaceAgentDevice, + // The composition root's surface stays behind `await import`, loaded on first request use. + createCommandSurface: async (config) => + (await import('./command-runtime/runtime-command-surface.ts')).createCommandSurfaceAgentDevice( + config, + ), }).catch((error) => { const appErr = asAppError(error); process.stderr.write(`Daemon error: ${appErr.message}\n`); diff --git a/src/daemon/__tests__/command-surface-fixture.ts b/src/daemon/__tests__/command-surface-fixture.ts index 050b0a7640..a4be8bb73c 100644 --- a/src/daemon/__tests__/command-surface-fixture.ts +++ b/src/daemon/__tests__/command-surface-fixture.ts @@ -1,10 +1,13 @@ import { createCommandSurfaceAgentDevice } from '../../command-runtime/runtime-command-surface.ts'; +import type { AgentDeviceRuntimeConfig } from '@agent-device/contracts/runtime-contract'; /** * The required `createCommandSurface` port for tests that build daemon param bags directly. * Tests reach the device through vi.mock'd seams, so a real factory behind the port never runs * command code in these suites; naming the port costs each call site one line, not a fake. - * The concrete function type (not the narrowed port alias) keeps runtime members like `signal` - * visible to the few tests that read them off an assembled runtime. + * The async wrapper matches the production port's load-on-first-use shape while keeping the + * concrete factory (not the narrowed port alias) so runtime members like `signal` stay visible + * to the few tests that read them off an assembled runtime. */ -export const testCreateCommandSurface = createCommandSurfaceAgentDevice; +export const testCreateCommandSurface = async (config: AgentDeviceRuntimeConfig) => + createCommandSurfaceAgentDevice(config); diff --git a/src/daemon/__tests__/generic-settle.test.ts b/src/daemon/__tests__/generic-settle.test.ts index 2247681a1c..d765c218e3 100644 --- a/src/daemon/__tests__/generic-settle.test.ts +++ b/src/daemon/__tests__/generic-settle.test.ts @@ -188,8 +188,8 @@ async function dispatchGeneric(params: { ...(params.flags ? { flags: params.flags } : {}), }; return await dispatchGenericCommand({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req, ref: params.sessionStore.lookup(params.sessionName)!, sessionName: params.sessionName, @@ -562,8 +562,8 @@ test('an ended generic lifetime is refused before dispatch or settle constructio mockCommandDispatch([AFTER_NODES]); await expect( dispatchGenericCommand({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, diff --git a/src/daemon/__tests__/is-runtime.test.ts b/src/daemon/__tests__/is-runtime.test.ts index ad66b85530..03e2b02d08 100644 --- a/src/daemon/__tests__/is-runtime.test.ts +++ b/src/daemon/__tests__/is-runtime.test.ts @@ -82,8 +82,8 @@ test('an admitted is inspects once, binds once, and answers through the bound ca ); const response = await dispatchIsViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: isRequest('is-bound', ['visible', 'id=auth_continue']), sessionName: 'is-bound', sessionStore, @@ -105,8 +105,8 @@ test('is absent uses the bound readAny capture for selector-first input without sessionStore.publish('is-absent', makeIosAppSession('is-absent')); const response = await dispatchIsViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: isRequest('is-absent', ['label="Removed row"', 'absent']), sessionName: 'is-absent', sessionStore, @@ -148,8 +148,8 @@ test('is absent bypasses a cached no-match snapshot before evaluating the bound ); const response = await dispatchIsViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: isRequest('is-absent-fresh', ['absent', 'label="Removed row"']), sessionName: 'is-absent-fresh', sessionStore, @@ -182,8 +182,8 @@ test('is absent fails closed for a quality-less legacy iOS root-only capture', a sessionStore.publish('is-legacy-sparse', makeIosAppSession('is-legacy-sparse')); const response = await dispatchIsViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: isRequest('is-legacy-sparse', ['absent', 'label="Removed row"']), sessionName: 'is-legacy-sparse', sessionStore, @@ -220,8 +220,8 @@ test('is absent rejects depth and scope before binding with typed invalid argume ['snapshotDepth', 2], ] as const) { const response = await dispatchIsViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: isRequest('is-absent-flags', ['absent', 'label="Gone"'], { [flag]: value }), sessionName: 'is-absent-flags', sessionStore, @@ -251,8 +251,8 @@ test('an unavailable capture fact refuses before any bind', async () => { sessionStore.publish('is-refused', makeAndroidSession('is-refused', { appBundleId: 'com.a' })); const response = await dispatchIsViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: isRequest('is-refused', ['visible', 'id=auth_continue']), sessionName: 'is-refused', sessionStore, @@ -285,8 +285,8 @@ test('an iOS session with no tracked app is refused with the open hint, not answ {}, async () => await dispatchIsViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: isRequest('is-no-app', ['visible', 'id=auth_continue']), sessionName: 'is-no-app', sessionStore, @@ -313,8 +313,8 @@ test('an iOS session WITH a tracked app still answers, so the refusal is the pla sessionStore.publish('is-with-app', makeIosAppSession('is-with-app')); const response = await dispatchIsViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: isRequest('is-with-app', ['visible', 'label=Continue']), sessionName: 'is-with-app', sessionStore, @@ -332,8 +332,8 @@ test('an Android session with no tracked app proceeds, because the owner adverti sessionStore.publish('is-android-no-app', makeAndroidSession('is-android-no-app')); const response = await dispatchIsViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: isRequest('is-android-no-app', ['visible', 'id=auth_continue']), sessionName: 'is-android-no-app', sessionStore, @@ -370,8 +370,8 @@ test('a refused request reaches the device by no route at all', async () => { }); const response = await dispatchIsViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: isRequest('is-direct-refused', ['selected', 'id="shipping-pickup"']), sessionName: 'is-direct-refused', sessionStore, @@ -432,8 +432,8 @@ test('a failing predicate answers COMMAND_FAILED from the bound capture', async }); const response = await dispatchIsViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: isRequest('is-direct-false', ['text', 'id=account_row', 'Wrong Expected Text']), sessionName: 'is-direct-false', sessionStore, @@ -474,8 +474,8 @@ test('a miss on a surface that never settled carries the unsettled fact, and the sessionStore.publish('is-unsettled', session); const isVisible = () => dispatchIsViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: isRequest('is-unsettled', ['visible', 'id=target']), sessionName: 'is-unsettled', sessionStore, @@ -518,8 +518,8 @@ test('a read after a scroll that moved nothing carries the no-effect outcome, an sessionStore.publish('is-no-effect', session); const isVisible = () => dispatchIsViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: isRequest('is-no-effect', ['visible', 'id=row']), sessionName: 'is-no-effect', sessionStore, diff --git a/src/daemon/__tests__/replay-maestro/session-replay-maestro-remote-evalscript.test.ts b/src/daemon/__tests__/replay-maestro/session-replay-maestro-remote-evalscript.test.ts index 0c20556aa6..099ec84266 100644 --- a/src/daemon/__tests__/replay-maestro/session-replay-maestro-remote-evalscript.test.ts +++ b/src/daemon/__tests__/replay-maestro/session-replay-maestro-remote-evalscript.test.ts @@ -73,8 +73,8 @@ async function runWithNetworkFlag(publicNetworkOnly: boolean | undefined) { // the asserted path: dropping it in the handler must fail this test, since an // unset field reads as trusted and a remote flow would run evalScript. const response = await handleReplayCommand({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req, sessionName: 'default', logPath: path.join(root, 'daemon.log'), diff --git a/src/daemon/__tests__/replay-suite/session-test-fail-fast.test.ts b/src/daemon/__tests__/replay-suite/session-test-fail-fast.test.ts index 7b7df2912c..0397ab597a 100644 --- a/src/daemon/__tests__/replay-suite/session-test-fail-fast.test.ts +++ b/src/daemon/__tests__/replay-suite/session-test-fail-fast.test.ts @@ -16,8 +16,8 @@ test('test --fail-fast continues after passing scripts', async () => { const invokedPaths: string[] = []; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/__tests__/replay-suite/session-test-runner.test.ts b/src/daemon/__tests__/replay-suite/session-test-runner.test.ts index 4ce91f039d..8d5dd03373 100644 --- a/src/daemon/__tests__/replay-suite/session-test-runner.test.ts +++ b/src/daemon/__tests__/replay-suite/session-test-runner.test.ts @@ -68,8 +68,8 @@ test('session_list includes device_udid and ios_simulator_device_set for iOS ses ); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', command: 'session_list', positionals: [] }, sessionName: 'default', logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), @@ -105,8 +105,8 @@ test('test filters replay scripts by context platform and skips untyped files', const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -177,8 +177,8 @@ test('test binds each replay script to its declared platform metadata', async () let response; try { response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -217,8 +217,8 @@ test('test cleans up suite-owned sessions after each executed script', async () fs.writeFileSync(path.join(root, '01-android.ad'), 'context platform=android\nopen "Demo"\n'); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -258,8 +258,8 @@ test('test retries failed scripts with fresh suite-owned sessions', async () => const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -313,8 +313,8 @@ test('test applies per-script timeout and writes attempt artifacts', async () => let invocationCount = 0; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -382,8 +382,8 @@ test('open does not retain a session when the request was canceled before comple markRequestCanceled(requestId); try { const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -415,8 +415,8 @@ test('test returns invalid args when no replay scripts match the platform filter fs.writeFileSync(path.join(root, '01-ios.ad'), 'context platform=ios\nopen "Settings"\n'); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -446,8 +446,8 @@ test('test rejects duplicate replay test metadata in the context header', async ); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/__tests__/replay-suite/session-test-suite-infrastructure.test.ts b/src/daemon/__tests__/replay-suite/session-test-suite-infrastructure.test.ts index 868b41c67d..98def34625 100644 --- a/src/daemon/__tests__/replay-suite/session-test-suite-infrastructure.test.ts +++ b/src/daemon/__tests__/replay-suite/session-test-suite-infrastructure.test.ts @@ -23,8 +23,8 @@ test('test --json marks a typed live device claim as infrastructure without retr let attempts = 0; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -72,8 +72,8 @@ test('test --json retries DEVICE_IN_USE without typed device-claim provenance', let attempts = 0; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/__tests__/replay-suite/session-test-suite.test.ts b/src/daemon/__tests__/replay-suite/session-test-suite.test.ts index 139bff496b..cb0770b657 100644 --- a/src/daemon/__tests__/replay-suite/session-test-suite.test.ts +++ b/src/daemon/__tests__/replay-suite/session-test-suite.test.ts @@ -65,8 +65,8 @@ test('test does not retry infrastructure startup failures and stops the suite', const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -114,8 +114,8 @@ test('test --fail-fast stops the suite after the first failure and leaves the re const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -162,8 +162,8 @@ test('test surfaces a suite-level failure when a source fails to parse', async ( const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -198,8 +198,8 @@ test('test discovers Maestro YAML suites when replay backend is set', async () = const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -235,8 +235,8 @@ test('test emits progress when attempts retry and pass', async () => { (event) => events.push(event), async () => await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -344,8 +344,8 @@ test('test stops before retrying when a rejected close leaves the prior macOS se let replayAttempts = 0; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -397,8 +397,8 @@ test('test stops retrying after maxAttempts when every attempt fails', async () let attemptCount = 0; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -442,8 +442,8 @@ test('test emits skip progress without synthetic duration', async () => { (event) => events.push(event), async () => await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -496,8 +496,8 @@ test('test aggregates snapshot diagnostics from replay session samples', async ( let captures = 0; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -556,8 +556,8 @@ test('test aggregates snapshot diagnostics from failed replay session samples', fs.writeFileSync(path.join(root, '01-fail.ad'), 'context platform=android\nopen "Demo"\n'); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -630,8 +630,8 @@ test('test stops the suite when the parent request is canceled during an active (event) => events.push(event), async () => await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -697,8 +697,8 @@ test('test --shard-all runs each runnable entry on each selected device', async async () => [ANDROID_ONE, ANDROID_TWO], async () => await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -774,8 +774,8 @@ test('test --shard-split distributes runnable entries by modulo and keeps skips async () => [ANDROID_TWO, ANDROID_ONE], async () => await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -824,8 +824,8 @@ test('test sharding rejects mutually exclusive shard modes', async () => { fs.writeFileSync(path.join(root, '01-a.ad'), 'context platform=android\nopen "Demo"\n'); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -852,8 +852,8 @@ test('test sharding rejects non-positive shard counts', async () => { fs.writeFileSync(path.join(root, '01-a.ad'), 'context platform=android\nopen "Demo"\n'); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -883,8 +883,8 @@ test('test sharding rejects fewer matched devices than requested shards', async async () => [ANDROID_ONE], async () => await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -919,8 +919,8 @@ test('test sharding does not require devices when every entry is skipped', async }, async () => await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/__tests__/request-handler-catalog.test.ts b/src/daemon/__tests__/request-handler-catalog.test.ts index dff4981887..9dfe0face0 100644 --- a/src/daemon/__tests__/request-handler-catalog.test.ts +++ b/src/daemon/__tests__/request-handler-catalog.test.ts @@ -508,8 +508,8 @@ async function runCatalogCommandThroughHandlerChain( async () => [], async () => await runRequestHandlerChain({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req, sessionName: req.session, logPath: '/tmp/agent-device-catalog-route.log', diff --git a/src/daemon/__tests__/request-handler-chain-provider-scope.test.ts b/src/daemon/__tests__/request-handler-chain-provider-scope.test.ts index 98d3f7e9bf..4d7b3934f6 100644 --- a/src/daemon/__tests__/request-handler-chain-provider-scope.test.ts +++ b/src/daemon/__tests__/request-handler-chain-provider-scope.test.ts @@ -77,8 +77,8 @@ test('the android adb executor from the generic provider scope reaches the sessi }); await runRequestHandlerChain({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), ...baseChainParams('provider-scope-test'), providerScope: { androidAdbExecutor }, }); @@ -94,8 +94,8 @@ test('an empty provider scope forwards no android adb executor to the session ha handleSessionCommandsMock.mockClear(); await runRequestHandlerChain({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), ...baseChainParams('provider-scope-empty'), providerScope: {}, }); diff --git a/src/daemon/__tests__/screenshot-runtime.test.ts b/src/daemon/__tests__/screenshot-runtime.test.ts index 9890107bd0..8ca9e0e6f0 100644 --- a/src/daemon/__tests__/screenshot-runtime.test.ts +++ b/src/daemon/__tests__/screenshot-runtime.test.ts @@ -70,8 +70,8 @@ test('admits one capture plan, binds once, and hands the runtime the resolved de const req = screenshotRequest({ positionals: [outPath] }); const resolved = await resolveScreenshotGenericExecution({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req, session, inspectFacts: fixture.inspectFacts, @@ -97,8 +97,8 @@ test('an iOS simulator session capture skips the redundant boot probe', async () const req = screenshotRequest({ positionals: [outPath] }); const resolved = await resolveScreenshotGenericExecution({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req, session, inspectFacts: fixture.inspectFacts, @@ -117,8 +117,8 @@ test('refuses an unavailable exact-owner capture fact before binding', async () const session = makeSession('default', { device: ANDROID_EMULATOR }); const resolved = await resolveScreenshotGenericExecution({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: screenshotRequest({ positionals: ['/tmp/unsupported.png'] }), session, inspectFacts: fixture.inspectFacts, @@ -148,8 +148,8 @@ test('--overlay-refs is refused up front when the target cannot capture a tree', const session = makeSession('default', { device: ANDROID_EMULATOR }); const resolved = await resolveScreenshotGenericExecution({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: screenshotRequest({ positionals: ['/tmp/overlay.png'], flags: { overlayRefs: true } }), session, inspectFacts: fixture.inspectFacts, @@ -182,8 +182,8 @@ test('the default destination is a reserved temp file, removed when the capture const req = screenshotRequest(); const resolved = await resolveScreenshotGenericExecution({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req, session, inspectFacts: fixture.inspectFacts, @@ -202,8 +202,8 @@ test('rejects the retired max-size field before inspecting owner facts', async ( await expect( resolveScreenshotGenericExecution({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: screenshotRequest({ positionals: ['/tmp/legacy.png'], flags: { screenshotMaxSize: 720 } as unknown as DaemonRequest['flags'], @@ -222,8 +222,8 @@ test('rejects --pixel-density outside iOS-family simulators before inspecting ow await expect( resolveScreenshotGenericExecution({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: screenshotRequest({ positionals: ['/tmp/android.png'], flags: { screenshotPixelDensity: 2 }, @@ -274,8 +274,8 @@ test('the crop runs after the platform write and before the shared scale', async }); const resolved = await resolveScreenshotGenericExecution({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req, session, inspectFacts: fixture.inspectFacts, @@ -324,8 +324,8 @@ test('a partial crop surfaces its warning once in the result record and annotate }); const resolved = await resolveScreenshotGenericExecution({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req, session, inspectFacts: fixture.inspectFacts, @@ -348,8 +348,8 @@ test('the crop plan is refused up front when the target cannot capture a tree', const session = makeSession('default', { device: ANDROID_EMULATOR }); const resolved = await resolveScreenshotGenericExecution({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: screenshotRequest({ positionals: ['/tmp/crop.png'], flags: { screenshotCropOn: 'label="Save"' }, @@ -385,8 +385,8 @@ test('the result record carries the display rotation the capture reported', asyn const req = screenshotRequest({ positionals: [outPath] }); const resolved = await resolveScreenshotGenericExecution({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req, session, inspectFacts: fixture.inspectFacts, @@ -406,8 +406,8 @@ test('the result record omits the display rotation when the capture reported non const req = screenshotRequest({ positionals: [outPath] }); const resolved = await resolveScreenshotGenericExecution({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req, session, inspectFacts: fixture.inspectFacts, diff --git a/src/daemon/__tests__/selector-capture-binding.test.ts b/src/daemon/__tests__/selector-capture-binding.test.ts index f065a07f4a..90b0732379 100644 --- a/src/daemon/__tests__/selector-capture-binding.test.ts +++ b/src/daemon/__tests__/selector-capture-binding.test.ts @@ -147,8 +147,8 @@ test('the bound construction path admits and binds before it builds a runtime', const resolved = await createBoundSelectorRuntime( { - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'bound', command: 'get', positionals: [], flags: {} }, sessionName: 'bound', logPath: '/tmp/bound.log', @@ -173,8 +173,8 @@ test('the bound construction path refuses an unavailable operation without build const resolved = await createBoundSelectorRuntime( { - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'bound', command: 'is', positionals: [], flags: {} }, sessionName: 'bound', logPath: '/tmp/bound.log', diff --git a/src/daemon/__tests__/selector-runtime-ambiguity-issuance.test.ts b/src/daemon/__tests__/selector-runtime-ambiguity-issuance.test.ts index 5227d1b11d..32f2a79988 100644 --- a/src/daemon/__tests__/selector-runtime-ambiguity-issuance.test.ts +++ b/src/daemon/__tests__/selector-runtime-ambiguity-issuance.test.ts @@ -111,8 +111,8 @@ test('an ambiguous is issues its printed candidates so press acts on the listed const fixture = selectorCaptureFixture({ snapshot: () => ambiguousFullCapture() }); const response = await dispatchIsViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: isRequest(sessionName, ['visible', 'label="Deploy"']), sessionName, sessionStore, @@ -138,8 +138,8 @@ test('an ambiguous is issues its printed candidates so press acts on the listed // (10,20) button the same body named before. const pinned = candidates[0]!.replace(/\s.*$/, '') + `~s${refsGeneration}`; const press = await handleInteractionCommands({ - - createCommandSurface: testCreateCommandSurface,req: { token: 't', session: sessionName, command: 'press', positionals: [pinned], flags: {} }, + createCommandSurface: testCreateCommandSurface, + req: { token: 't', session: sessionName, command: 'press', positionals: [pinned], flags: {} }, sessionName, sessionStore, contextFromFlags, @@ -158,8 +158,8 @@ test('a plain candidate ref from the ambiguity refusal is refused, not silently const fixture = selectorCaptureFixture({ snapshot: () => ambiguousFullCapture() }); const response = await dispatchIsViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: isRequest(sessionName, ['visible', 'label="Deploy"']), sessionName, sessionStore, @@ -173,8 +173,8 @@ test('a plain candidate ref from the ambiguity refusal is refused, not silently // plain (unpinned) ref needs a complete frame and is refused with the // suggested pinned form rather than resolving positionally. const press = await handleInteractionCommands({ - - createCommandSurface: testCreateCommandSurface,req: { token: 't', session: sessionName, command: 'press', positionals: ['@e2'], flags: {} }, + createCommandSurface: testCreateCommandSurface, + req: { token: 't', session: sessionName, command: 'press', positionals: ['@e2'], flags: {} }, sessionName, sessionStore, contextFromFlags, @@ -223,8 +223,8 @@ test('an ambiguous is on a sparse capture prints no candidates and issues nothin }); const fixture = selectorCaptureFixture({ snapshot: sparseCapture }); const response = await dispatchIsViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: isRequest(sessionName, ['visible', 'label="Deploy"']), sessionName, sessionStore, @@ -294,8 +294,8 @@ test('an ambiguous press issues its printed candidates from its own capture', as }); const response = await handleInteractionCommands({ - - createCommandSurface: testCreateCommandSurface,req: { + createCommandSurface: testCreateCommandSurface, + req: { token: 't', session: sessionName, command: 'press', diff --git a/src/daemon/__tests__/selector-runtime.test.ts b/src/daemon/__tests__/selector-runtime.test.ts index 0a20dfb555..329830273a 100644 --- a/src/daemon/__tests__/selector-runtime.test.ts +++ b/src/daemon/__tests__/selector-runtime.test.ts @@ -103,8 +103,8 @@ test.each(['rebuild', 'retire'] as const)( [Symbol.asyncDispose]: async () => {}, }; const running = dispatchGetViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/__tests__/snapshot-command-runtime.test.ts b/src/daemon/__tests__/snapshot-command-runtime.test.ts index b2eca36d16..bd98b1ab57 100644 --- a/src/daemon/__tests__/snapshot-command-runtime.test.ts +++ b/src/daemon/__tests__/snapshot-command-runtime.test.ts @@ -74,14 +74,14 @@ for (const command of ['snapshot', 'diff snapshot'] as const) { const running = command === 'snapshot' ? dispatchSnapshotViaRuntime({ - - createCommandSurface: testCreateCommandSurface,...input, + createCommandSurface: testCreateCommandSurface, + ...input, ...snapshotRuntimeFixture(requestId), platformServices: daemonPlatformServicesFixture(), }) : dispatchSnapshotDiffViaRuntime({ - - createCommandSurface: testCreateCommandSurface,...input, + createCommandSurface: testCreateCommandSurface, + ...input, ...snapshotRuntimeFixture(requestId), platformServices: daemonPlatformServicesFixture(), }); @@ -117,8 +117,8 @@ for (const change of ['unchanged', 'rebuild', 'replace'] as const) { }; }); const running = dispatchSnapshotViaRuntime({ - - createCommandSurface: testCreateCommandSurface,req: { command: 'snapshot', positionals: [], token: 't', session: address }, + createCommandSurface: testCreateCommandSurface, + req: { command: 'snapshot', positionals: [], token: 't', session: address }, sessionName: address, logPath: '/dev/null', sessionStore, @@ -173,8 +173,8 @@ test('a composed snapshot refuses its supplied retired lifetime before facts or captureMock.mockResolvedValue({ nodes: [], truncated: false, backend: 'uiautomator' }); await expect( dispatchSnapshotViaRuntime({ - - createCommandSurface: testCreateCommandSurface,req: { command: 'snapshot', positionals: [], token: 't', session: 'default' }, + createCommandSurface: testCreateCommandSurface, + req: { command: 'snapshot', positionals: [], token: 't', session: 'default' }, sessionName: address, sessionRef: ref, logPath: '/dev/null', diff --git a/src/daemon/__tests__/snapshot-diff-runtime.test.ts b/src/daemon/__tests__/snapshot-diff-runtime.test.ts index 7e58bf7a04..a78610a592 100644 --- a/src/daemon/__tests__/snapshot-diff-runtime.test.ts +++ b/src/daemon/__tests__/snapshot-diff-runtime.test.ts @@ -128,8 +128,8 @@ test.each([ }); const response = await dispatchSnapshotDiffViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { command: 'diff', positionals: ['snapshot'], @@ -189,8 +189,8 @@ test('preserves initialized, unchanged, and changed diff results through one bou const run = async () => await dispatchSnapshotDiffViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { command: 'diff', positionals: ['snapshot'], @@ -239,8 +239,8 @@ test('a sparse internal diff capture returns no screenshot fallback artifact', a sessionStore.publish('diff-runtime', { ...harness.session, appBundleId: 'com.example.app' }); const response = await dispatchSnapshotDiffViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { command: 'diff', positionals: ['snapshot'], token: 't', session: 'diff-runtime' }, sessionName: 'diff-runtime', logPath: '/tmp/diff-runtime.log', diff --git a/src/daemon/__tests__/snapshot-runtime-binding.test.ts b/src/daemon/__tests__/snapshot-runtime-binding.test.ts index 7a59458279..980a9b6b06 100644 --- a/src/daemon/__tests__/snapshot-runtime-binding.test.ts +++ b/src/daemon/__tests__/snapshot-runtime-binding.test.ts @@ -44,8 +44,8 @@ test('the owning interface binds exactly the session device the facts were admit const resolved = await resolveBoundSnapshotCaptureRuntime( { - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req, sessionName: 'bind-test', logPath: '/tmp/bind-test.log', diff --git a/src/daemon/__tests__/sparse-fallback-screenshot.test.ts b/src/daemon/__tests__/sparse-fallback-screenshot.test.ts index 0e768d238d..7a2c346eab 100644 --- a/src/daemon/__tests__/sparse-fallback-screenshot.test.ts +++ b/src/daemon/__tests__/sparse-fallback-screenshot.test.ts @@ -58,8 +58,8 @@ async function dispatch( internalObservation = false, ) { const response = await dispatchSnapshotViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { command: 'snapshot', positionals: [], diff --git a/src/daemon/__tests__/wait-runtime.test.ts b/src/daemon/__tests__/wait-runtime.test.ts index 530dcc835f..919a96a7e2 100644 --- a/src/daemon/__tests__/wait-runtime.test.ts +++ b/src/daemon/__tests__/wait-runtime.test.ts @@ -160,8 +160,8 @@ test.each(['rebuild', 'retire'] as const)( const address = 'cwd:sleep-wait:default'; const ref = sessionStore.publish(address, makeSession('default', { device: harness.device })); const running = handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: waitRequest(['100']), sessionName: address, logPath: '/tmp/daemon.log', @@ -216,8 +216,8 @@ async function runWait( } sessionStore.publish(session.name, session); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: waitRequest(positionals, flags), sessionName: session.name, logPath: '/tmp/daemon.log', @@ -472,8 +472,8 @@ test('a read after a natively satisfied text wait captures instead of reusing th expect(harness.captureSnapshot).toHaveBeenCalledOnce(); const read = await dispatchGetViaRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { command: 'get', positionals: ['text', 'label="cold.start"'], diff --git a/src/daemon/generic-settle.ts b/src/daemon/generic-settle.ts index 1ab5b94bc1..91fc2e97ff 100644 --- a/src/daemon/generic-settle.ts +++ b/src/daemon/generic-settle.ts @@ -103,7 +103,7 @@ async function observeSettled( settle: SettleParams, baseline: SurfaceScopedNodes, ): Promise { - const runtime = createGenericSettleRuntime(context); + const runtime = await createGenericSettleRuntime(context); if (!runtime) return undefined; // R2: the daemon reaches the settle engine through the runtime command // surface, never by importing `commands/` — the same seam the touch handlers @@ -120,11 +120,11 @@ async function observeSettled( return refsGeneration === undefined ? observation : { ...observation, refsGeneration }; } -function createGenericSettleRuntime( +async function createGenericSettleRuntime( context: GenericSettleContext, -): ReturnType | undefined { +): Promise> | undefined> { try { - return createInteractionRuntime({ + return await createInteractionRuntime({ createCommandSurface: context.createCommandSurface, req: context.req, sessionName: context.sessionName, diff --git a/src/daemon/handlers/__tests__/session-appstate-input.test.ts b/src/daemon/handlers/__tests__/session-appstate-input.test.ts index 4f200b8066..c5b36b4629 100644 --- a/src/daemon/handlers/__tests__/session-appstate-input.test.ts +++ b/src/daemon/handlers/__tests__/session-appstate-input.test.ts @@ -56,8 +56,8 @@ test('appstate on iOS requires active session on selected device', async () => { mockDispatch.mockRejectedValue(new Error('snapshot dispatch should not run')); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -104,8 +104,8 @@ test('appstate returns session appName when bundle id is unavailable', async () mockDispatch.mockRejectedValue(new Error('snapshot dispatch should not run')); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -155,8 +155,8 @@ test('appstate fails when iOS session has no tracked app', async () => { mockResolveTargetDevice.mockResolvedValue(selectedDevice); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -190,8 +190,8 @@ test('appstate without session on iOS selector returns SESSION_NOT_FOUND', async mockResolveTargetDevice.mockResolvedValue(selectedDevice); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -215,8 +215,8 @@ test('appstate without session on iOS selector returns SESSION_NOT_FOUND', async test('appstate with explicit missing session returns SESSION_NOT_FOUND', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'sim', @@ -242,8 +242,8 @@ test('appstate with explicit missing session returns SESSION_NOT_FOUND', async ( test('clipboard requires an active session or explicit device selector', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -284,8 +284,8 @@ test('clipboard rejects unsupported iOS physical devices', async () => { mockDispatch.mockRejectedValue(new Error('dispatch should not run for unsupported targets')); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -371,8 +371,8 @@ test('appstate on iOS reads the session app state from the runner when its owner }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: iosSessionRequest(sessionName), sessionName, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), @@ -404,8 +404,8 @@ test('appstate on iOS keeps the session answer, with no state, when no runner is const runtime = appleAppStateRuntime(async () => ({})); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: iosSessionRequest(sessionName), sessionName, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), @@ -435,8 +435,8 @@ test('appstate on iOS keeps the session answer, with no state, when the runner c }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: iosSessionRequest(sessionName), sessionName, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), diff --git a/src/daemon/handlers/__tests__/session-boot-shutdown.test.ts b/src/daemon/handlers/__tests__/session-boot-shutdown.test.ts index 2dbc6bdda5..a0e9870cff 100644 --- a/src/daemon/handlers/__tests__/session-boot-shutdown.test.ts +++ b/src/daemon/handlers/__tests__/session-boot-shutdown.test.ts @@ -24,8 +24,8 @@ import { testCreateCommandSurface } from '../../__tests__/command-surface-fixtur test('boot requires session or explicit selector', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -68,8 +68,8 @@ test('boot prefers explicit device selector over active session device', async ( mockResolveTargetDevice.mockResolvedValue(selectedDevice); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -114,8 +114,8 @@ test('boot --timeout forwards a startup deadline to bootTarget (#3004)', async ( const beforeMs = Date.now(); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -149,8 +149,8 @@ test('boot without --timeout leaves the startup deadline unset', async () => { mockResolveTargetDevice.mockResolvedValue(selectedDevice); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -186,8 +186,8 @@ test('boot --headless admits a stopped Android emulator through facts and binds booted: true, }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -232,8 +232,8 @@ test('boot rejects the macOS host boot cell after one facts inspection and befor mockResolveTargetDevice.mockResolvedValue(device); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -270,8 +270,8 @@ test('boot admits a stopped Android emulator through normal readiness', async () booted: true, }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -309,8 +309,8 @@ test('boot forwards Android serial admission policy to readiness', async () => { }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -345,8 +345,8 @@ test('boot --headless requires avd selector when device cannot be resolved', asy const sessionStore = makeSessionStore(); mockResolveTargetDevice.mockRejectedValue(new AppError('DEVICE_NOT_FOUND', 'No device found')); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -382,8 +382,8 @@ test('boot --headless rejects non-Android selectors', async () => { booted: false, }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -417,8 +417,8 @@ test('boot keeps --target validation before facts inspection', async () => { booted: false, }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -455,8 +455,8 @@ test('shutdown turns off selected iOS simulator', async () => { mockResolveTargetDevice.mockResolvedValue(selectedDevice); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -503,8 +503,8 @@ test('shutdown rejects active session device and points to close --shutdown', as mockResolveTargetDevice.mockResolvedValue(selectedDevice); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -544,8 +544,8 @@ test('shutdown turns off selected Android emulator', async () => { }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -593,8 +593,8 @@ test('shutdown rejects unsupported physical devices', async () => { }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -638,8 +638,8 @@ test('shutdown returns an error response when selected target shutdown fails', a }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/handlers/__tests__/session-command-replay.test.ts b/src/daemon/handlers/__tests__/session-command-replay.test.ts index 80ed07e04a..6e775e9d6c 100644 --- a/src/daemon/handlers/__tests__/session-command-replay.test.ts +++ b/src/daemon/handlers/__tests__/session-command-replay.test.ts @@ -18,8 +18,8 @@ test('replay parses open --relaunch flag and replays open with relaunch semantic const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -58,8 +58,8 @@ test('replay parses runtime set flags and replays runtime command', async () => const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -99,8 +99,8 @@ test('replay parses inline open runtime flags and replays open with runtime payl const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -145,8 +145,8 @@ test('replay delivers scroll --until and wait --raw flags to the dispatched step const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -182,8 +182,8 @@ test('replay inherits parent device selectors for each invoked step', async () = const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -223,8 +223,8 @@ test('replay inherits the parent web platform selector for each invoked step', a const invoked: DaemonRequest[] = []; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -268,8 +268,8 @@ test('test --platform web reports no matching scripts, typed or untyped, because fs.writeFileSync(path.join(root, '02-android.ad'), 'context platform=android\nopen "Demo"\n'); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/handlers/__tests__/session-device-claims.test.ts b/src/daemon/handlers/__tests__/session-device-claims.test.ts index babe863038..606a60ac8d 100644 --- a/src/daemon/handlers/__tests__/session-device-claims.test.ts +++ b/src/daemon/handlers/__tests__/session-device-claims.test.ts @@ -147,8 +147,8 @@ test('failed local open before device setup rolls its device claim back', async await assert.rejects( async () => handleOpenCommand({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', @@ -176,8 +176,8 @@ test('failed local open after dispatch retains its device claim for recovery', a await assert.rejects( async () => handleOpenCommand({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', @@ -208,8 +208,8 @@ test('failed local runtime-hint setup retains its device claim before open dispa await assert.rejects( async () => handleOpenCommand({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', @@ -236,8 +236,8 @@ test('failed local open response rolls its device claim back', async () => { mockResolveTargetDevice.mockResolvedValue(android); const response = await handleOpenCommand({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', @@ -264,8 +264,8 @@ test('cancellation after local device setup retains the device claim for recover try { const response = await handleOpenCommand({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', @@ -297,8 +297,8 @@ test('a canceled attempt lets the next attempt of the same suite open the device markRequestCanceled(requestId); try { const timedOut = await handleOpenCommand({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', @@ -318,8 +318,8 @@ test('a canceled attempt lets the next attempt of the same suite open the device assert.equal(store.get('suite:1-gesture-pan-duration:attempt-1'), undefined); const retry = await handleOpenCommand({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', @@ -348,8 +348,8 @@ test('provider-owned open creates no host-local device claim from its selected o mockDispatch.mockResolvedValue(undefined); const response = await handleOpenCommand({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', @@ -386,8 +386,8 @@ test('a foreign live claim rejects open before platform preparation or mutation' if (acquired.status !== 'acquired') return; const response = await handleOpenCommand({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', @@ -561,8 +561,8 @@ test('an open under a macos-app lease holds only its app, beside other claims on }); const response = await handleOpenCommand({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { command: 'open', token: 'test', diff --git a/src/daemon/handlers/__tests__/session-install-source-ref-frame.test.ts b/src/daemon/handlers/__tests__/session-install-source-ref-frame.test.ts index c1912f452b..7c5387b873 100644 --- a/src/daemon/handlers/__tests__/session-install-source-ref-frame.test.ts +++ b/src/daemon/handlers/__tests__/session-install-source-ref-frame.test.ts @@ -116,8 +116,8 @@ async function dispatchInstallSource(params: { meta: { installSource: { kind: 'path', path: '/tmp/App.apk' } }, }; return await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req, sessionName: 'default', logPath: '/tmp/daemon.log', diff --git a/src/daemon/handlers/__tests__/session-push-ref-frame.test.ts b/src/daemon/handlers/__tests__/session-push-ref-frame.test.ts index 2774a80ffc..37a2629a68 100644 --- a/src/daemon/handlers/__tests__/session-push-ref-frame.test.ts +++ b/src/daemon/handlers/__tests__/session-push-ref-frame.test.ts @@ -91,8 +91,8 @@ async function dispatchPush(params: { flags: {}, }; return await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req, sessionName: 'default', logPath: '/tmp/daemon.log', diff --git a/src/daemon/handlers/__tests__/session-push.test.ts b/src/daemon/handlers/__tests__/session-push.test.ts index 1012dec210..0d495a1757 100644 --- a/src/daemon/handlers/__tests__/session-push.test.ts +++ b/src/daemon/handlers/__tests__/session-push.test.ts @@ -29,8 +29,8 @@ const invoke = async (_req: DaemonRequest): Promise => { test('push requires active session or explicit device selector', async () => { const sessionStore = makeSessionStore('agent-device-session-push-'); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -68,8 +68,8 @@ test('push validates payload before runtime facts admission', async () => { await expect( handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -110,8 +110,8 @@ test('push runs readiness and notification through one admitted runtime binding' }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -177,8 +177,8 @@ test('push treats an existing brace-prefixed payload as a file before inline JSO try { const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -217,8 +217,8 @@ test('push stops at unavailable facts without binding', async () => { }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -273,8 +273,8 @@ test('push fails closed before binding when a provider owner lacks readiness', a }; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/handlers/__tests__/session-reinstall.test.ts b/src/daemon/handlers/__tests__/session-reinstall.test.ts index d5e76d21db..f471767059 100644 --- a/src/daemon/handlers/__tests__/session-reinstall.test.ts +++ b/src/daemon/handlers/__tests__/session-reinstall.test.ts @@ -50,8 +50,8 @@ beforeEach(() => { test('reinstall requires active session or explicit device selector', async () => { const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -86,8 +86,8 @@ test('reinstall validates required args before runtime admission', async () => { }), ); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -124,8 +124,8 @@ test('install binds the deployment use exactly once after one facts admission', }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: session.name, @@ -189,8 +189,8 @@ test('install fails closed before binding when its provider owner lacks deployme }; const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: session.name, @@ -235,8 +235,8 @@ test('reinstall cleans an uploaded artifact after the request-scoped operation', vi.mocked(resolveTargetDevice).mockResolvedValue(session.device); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: session.name, @@ -271,8 +271,8 @@ test('HarmonyOS reinstall updates the active session identity from the runtime r mockDeployAppRuntime.mockResolvedValue({ packageName: 'com.example.application' }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: session.name, @@ -322,8 +322,8 @@ test.each(['rebuild', 'retire'] as const)( return { packageName: 'com.example.updated' }; }); const running = handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/handlers/__tests__/session-relaunch-guards.test.ts b/src/daemon/handlers/__tests__/session-relaunch-guards.test.ts index cabf31e2a5..fe8ee4fc86 100644 --- a/src/daemon/handlers/__tests__/session-relaunch-guards.test.ts +++ b/src/daemon/handlers/__tests__/session-relaunch-guards.test.ts @@ -17,8 +17,8 @@ import { testCreateCommandSurface } from '../../__tests__/command-surface-fixtur test('open --relaunch rejects URL targets', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -43,8 +43,8 @@ test('open --relaunch rejects URL targets', async () => { test('open --relaunch fails without app when no session exists', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -69,8 +69,8 @@ test('open --relaunch fails without app when no session exists', async () => { test('open --relaunch rejects Android app binary paths', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -93,8 +93,8 @@ test('open --relaunch rejects Android app binary paths', async () => { test('open --relaunch rejects bare Android app binary filenames', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -128,8 +128,8 @@ test('open --relaunch rejects Android app binary paths for active sessions', asy sessionStore.publish('default', session); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -152,8 +152,8 @@ test('open --relaunch rejects Android app binary paths for active sessions', asy test('open --relaunch rejects Android app binary paths before resolving a new device', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -197,8 +197,8 @@ test('open on in-use device returns DEVICE_IN_USE before readiness checks', asyn }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -253,8 +253,8 @@ test('open on device owned by recording session returns recording recovery hint' }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'test-attempt', diff --git a/src/daemon/handlers/__tests__/session-runtime-command.test.ts b/src/daemon/handlers/__tests__/session-runtime-command.test.ts index 790a9bcff7..08699dbbc3 100644 --- a/src/daemon/handlers/__tests__/session-runtime-command.test.ts +++ b/src/daemon/handlers/__tests__/session-runtime-command.test.ts @@ -32,8 +32,8 @@ test('runtime set/show/clear manages session-scoped runtime hints before open', } satisfies Pick; const setResponse = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { ...baseRequest, command: 'runtime', @@ -53,8 +53,8 @@ test('runtime set/show/clear manages session-scoped runtime hints before open', expect(setResponse?.ok).toBe(true); const showResponse = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { ...baseRequest, command: 'runtime', @@ -79,8 +79,8 @@ test('runtime set/show/clear manages session-scoped runtime hints before open', } const clearResponse = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { ...baseRequest, command: 'runtime', @@ -116,8 +116,8 @@ test('runtime clear removes applied transport hints for the active app', async ( }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -167,8 +167,8 @@ test('runtime clear expires the ref frame at the admitted hint mutation boundary }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -222,8 +222,8 @@ test('runtime clear rejects a false runtime-hints fact before its one implementa }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -274,8 +274,8 @@ test('runtime gesture-viewport admits and binds the exact viewport operation onc gestureRuntimeSpies.gestureViewport.mockClear(); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -344,8 +344,8 @@ for (const phase of ['admission', 'effect'] as const) { } await expect( handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -387,8 +387,8 @@ test('runtime clear expires and uses the latest matching record after admission' return lifecycleRuntimeFacts(target); }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: ref.address, diff --git a/src/daemon/handlers/__tests__/session-selector-dispatch.test.ts b/src/daemon/handlers/__tests__/session-selector-dispatch.test.ts index babc0a9026..e999876611 100644 --- a/src/daemon/handlers/__tests__/session-selector-dispatch.test.ts +++ b/src/daemon/handlers/__tests__/session-selector-dispatch.test.ts @@ -77,8 +77,8 @@ test.each(['rebuild', 'retire'] as const)( [Symbol.asyncDispose]: async () => {}, }; const running = handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -179,8 +179,8 @@ test('keyboard dismiss crosses the ADR 0014 seam while keyboard status preserves // dismiss mutates the device → frame expires. const dismissRef = sessionStore.publish(sessionName, makeSession(sessionName, device)); await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -201,8 +201,8 @@ test('keyboard dismiss crosses the ADR 0014 seam while keyboard status preserves sessionStore.retire(dismissRef); sessionStore.publish(sessionName, makeSession(sessionName, device)); await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -246,8 +246,8 @@ test('keyboard dismiss expires the frame before the invocation runs, even when i sessionStore.publish(sessionName, makeSession(sessionName, device)); await expect( handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -269,8 +269,8 @@ test('keyboard dismiss expires the frame before the invocation runs, even when i test('keyboard requires an active session or explicit device selector', async () => { const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', @@ -298,8 +298,8 @@ test('keyboard dismiss requires active iOS session for explicit selectors', asyn const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/handlers/__tests__/session-state-boot-warmup.test.ts b/src/daemon/handlers/__tests__/session-state-boot-warmup.test.ts index 555f19d1e8..9dc700aa65 100644 --- a/src/daemon/handlers/__tests__/session-state-boot-warmup.test.ts +++ b/src/daemon/handlers/__tests__/session-state-boot-warmup.test.ts @@ -21,8 +21,8 @@ test('boot leaves Apple keep-hot policy inside the platform runtime', async () = }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/handlers/__tests__/session-trigger.test.ts b/src/daemon/handlers/__tests__/session-trigger.test.ts index d28734d11f..0b504026ad 100644 --- a/src/daemon/handlers/__tests__/session-trigger.test.ts +++ b/src/daemon/handlers/__tests__/session-trigger.test.ts @@ -16,8 +16,8 @@ const invoke = async (_req: DaemonRequest): Promise => { test('trigger-app-event requires active session or explicit device selector', async () => { const sessionStore = makeSessionStore('agent-device-session-trigger-'); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/handlers/__tests__/snapshot-handler-freshness.test.ts b/src/daemon/handlers/__tests__/snapshot-handler-freshness.test.ts index 7c7621bec8..cb3cb34c1b 100644 --- a/src/daemon/handlers/__tests__/snapshot-handler-freshness.test.ts +++ b/src/daemon/handlers/__tests__/snapshot-handler-freshness.test.ts @@ -179,8 +179,8 @@ test('snapshot annotations survive a deferred post-gesture capture into CLI JSON }); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot'), sessionName, logPath: '/tmp/daemon.log', @@ -211,8 +211,8 @@ test('snapshot timeout captures Android screenshot evidence with overlay refs', const sessionStore = makeAndroidTimeoutEvidenceSession(sessionName); mockAndroidTimeoutEvidenceDispatch(); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot'), sessionName, logPath: '/tmp/daemon.log', @@ -242,8 +242,8 @@ test('snapshot warns when recent snapshot node count collapses sharply', async ( ); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot'), sessionName, logPath: '/tmp/daemon.log', @@ -290,8 +290,8 @@ test('snapshot does not warn on expected node drop across presentation modes', a }); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot', { flags: { snapshotInteractiveOnly: true } }), sessionName, logPath: '/tmp/daemon.log', @@ -329,8 +329,8 @@ test('snapshot automatically retries stale Android trees after recent navigation ); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot', { flags: { snapshotInteractiveOnly: true } }), sessionName, logPath: '/tmp/daemon.log', @@ -359,8 +359,8 @@ test('snapshot warns when Android freshness retries still return the previous ro ); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot', { flags: { snapshotInteractiveOnly: true } }), sessionName, logPath: '/tmp/daemon.log', @@ -409,8 +409,8 @@ test('snapshot response includes normalized visibility metadata', async () => { }); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot', { flags: { snapshotInteractiveOnly: true } }), sessionName, logPath: '/tmp/daemon.log', @@ -439,8 +439,8 @@ test('diff snapshot carries stale-tree warnings for recent Android presses', asy ); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'diff', { positionals: ['snapshot'], flags: { snapshotInteractiveOnly: true }, diff --git a/src/daemon/handlers/__tests__/snapshot-handler.test.ts b/src/daemon/handlers/__tests__/snapshot-handler.test.ts index 70d46026cc..6a85a194f6 100644 --- a/src/daemon/handlers/__tests__/snapshot-handler.test.ts +++ b/src/daemon/handlers/__tests__/snapshot-handler.test.ts @@ -83,8 +83,8 @@ test('snapshot rejects @ref scope without existing session snapshot', async () = ); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot', { flags: { snapshotScope: '@e1' } }), sessionName, logPath: '/tmp/daemon.log', @@ -106,8 +106,8 @@ test('snapshot on iOS rejects sessions without a tracked app', async () => { const runtime = countingSnapshotRuntime(); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot'), sessionName, logPath: '/tmp/daemon.log', @@ -138,8 +138,8 @@ test('snapshot on iOS without a tracked app carries the detected open command as const platformServices = daemonPlatformServicesFixture(); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices, + createCommandSurface: testCreateCommandSurface, + platformServices, req: snapshotRequest(sessionName, 'snapshot'), sessionName, logPath: '/tmp/daemon.log', @@ -174,8 +174,8 @@ test('snapshot on provider-backed iOS runs without a tracked app', async () => { const runtime = countingSnapshotRuntime(); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot'), sessionName, logPath: '/tmp/daemon.log', @@ -199,8 +199,8 @@ test('diff on local iOS still requires a tracked app', async () => { sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'diff', { positionals: ['snapshot'] }), sessionName, logPath: '/tmp/daemon.log', @@ -229,8 +229,8 @@ test('snapshot on iOS runs when the session tracks an app', async () => { }); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot'), sessionName, logPath: '/tmp/daemon.log', @@ -266,8 +266,8 @@ test('snapshot re-activates a complete frame; diff preserves it (ADR 0014)', asy }); const snapshotResponse = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot'), sessionName, logPath: '/tmp/daemon.log', @@ -280,8 +280,8 @@ test('snapshot re-activates a complete frame; diff preserves it (ADR 0014)', asy expect(refFrameState(sessionStore.get(sessionName)!)).toBe('active'); const diffResponse = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'diff', { positionals: ['snapshot'] }), sessionName, logPath: '/tmp/daemon.log', @@ -301,8 +301,8 @@ async function runVersionedRefsCommand(params: { command: 'snapshot' | 'diff'; }): Promise | undefined> { const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(params.sessionName, params.command, { positionals: params.command === 'diff' ? ['snapshot'] : [], }), @@ -388,8 +388,8 @@ test('daemon-private snapshot observation advances capture state without publish }); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot', { internal: { observationOnly: true } }), sessionName, logPath: '/tmp/daemon.log', @@ -412,8 +412,8 @@ test('snapshot surfaces filtered-to-zero Android guidance for interactive snapsh legacyDispatchCapture.mockResolvedValue(androidCapture([], { rawNodeCount: 42, maxDepth: 8 })); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'snapshot', { flags: { snapshotInteractiveOnly: true, snapshotDepth: 3 }, }), @@ -434,8 +434,8 @@ test('snapshot surfaces filtered-to-zero Android guidance for interactive snapsh test('diff rejects unsupported kind', async () => { const sessionStore = makeSessionStore(); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest('default', 'diff', { positionals: ['unknown'] }), sessionName: 'default', logPath: '/tmp/daemon.log', @@ -453,8 +453,8 @@ test('diff rejects unsupported kind', async () => { test('diff screenshot is not handled daemon-side (client-backed command)', async () => { const sessionStore = makeSessionStore(); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest('default', 'diff', { positionals: ['screenshot'] }), sessionName: 'default', logPath: '/tmp/daemon.log', diff --git a/src/daemon/handlers/__tests__/snapshot-settings-handler.test.ts b/src/daemon/handlers/__tests__/snapshot-settings-handler.test.ts index b3d47a8bcf..0ba62d3a17 100644 --- a/src/daemon/handlers/__tests__/snapshot-settings-handler.test.ts +++ b/src/daemon/handlers/__tests__/snapshot-settings-handler.test.ts @@ -96,8 +96,8 @@ test('settings rejects unsupported iOS physical devices', async () => { ); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['wifi', 'on'] }), sessionName, logPath: '/tmp/daemon.log', @@ -118,8 +118,8 @@ test('settings clear-app-state dispatches explicit app id without an active app sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['clear-app-state', 'org.reactnavigation.playground'], }), @@ -142,8 +142,8 @@ test('settings clear-app-state rejects missing app id when no app session is bou sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['clear-app-state'] }), sessionName, logPath: '/tmp/daemon.log', @@ -166,8 +166,8 @@ test('settings reset-keychain dispatches without an app id or active app session sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['reset-keychain', 'clear'], }), @@ -189,8 +189,8 @@ test('settings reset-keychain rejects an extra app argument instead of dropping sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['reset-keychain', 'clear', 'com.example.app'], }), @@ -212,8 +212,8 @@ test('settings text-size reads the category the owner holds without mutating any sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['text-size'] }), sessionName, logPath: '/tmp/daemon.log', @@ -237,16 +237,16 @@ test('settings text-size refuses the macOS host on both legs with the same code' sessionStore.publish(sessionName, makeSession(sessionName, macOsDevice)); const read = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['text-size'] }), sessionName, logPath: '/tmp/daemon.log', sessionStore, }); const write = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['text-size', 'large'] }), sessionName, logPath: '/tmp/daemon.log', @@ -272,8 +272,8 @@ test('settings text-size applies a ladder category through the write leg', async sessionStore.publish(sessionName, session); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['text-size', 'accessibility-extra-large'], }), @@ -306,8 +306,8 @@ test('settings text-size refuses an Apple leaf with no content size before it ex for (const positionals of [['text-size'], ['text-size', 'large']]) { const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals }), sessionName, logPath: '/tmp/daemon.log', @@ -333,8 +333,8 @@ test('settings text-size refuses an off-ladder category with the whole ladder', sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['text-size', 'gigantic'] }), sessionName, logPath: '/tmp/daemon.log', @@ -357,8 +357,8 @@ test('settings text-size refuses an off-ladder category with the whole ladder', test('settings usage hint documents canonical faceid states', async () => { const sessionStore = makeSessionStore(); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest('default', 'settings'), sessionName: 'default', logPath: '/tmp/daemon.log', @@ -382,8 +382,8 @@ test('settings on macOS rejects wifi before dispatch with explicit subset guidan sessionStore.publish(sessionName, makeSession(sessionName, macOsDevice)); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['wifi', 'on'] }), sessionName, logPath: '/tmp/daemon.log', @@ -414,8 +414,8 @@ test('settings permission grant targets an explicit app with no session app boun sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['permission', 'grant', 'camera'], input: { app: 'com.example.app' }, @@ -445,8 +445,8 @@ test('settings permission grant keeps the session app when no app is named', asy ); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['permission', 'grant', 'camera'], }), @@ -470,8 +470,8 @@ test('settings permission grant prefers an explicit app over the session app', a ); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['permission', 'grant', 'camera'], input: { app: 'com.other.app' }, @@ -491,8 +491,8 @@ test('settings location on targets an explicit app on an iOS simulator', async ( sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['location', 'on'], input: { app: 'com.example.app' }, @@ -516,8 +516,8 @@ test('settings Android permission grant targets an explicit app with no session sessionStore.publish(sessionName, makeSession(sessionName, androidDevice)); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['permission', 'grant', 'camera'], input: { app: 'com.example.app' }, @@ -543,8 +543,8 @@ test('settings Android location on refuses a named app before reaching the owner sessionStore.publish(sessionName, makeSession(sessionName, androidDevice)); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['location', 'on'], input: { app: 'com.example.app' }, @@ -577,8 +577,8 @@ test('settings iOS location 1 targets an explicit app over the session app', asy ); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['location', '1'], input: { app: 'com.example.app' }, @@ -602,8 +602,8 @@ test('settings Android location true refuses a named app before reaching the own sessionStore.publish(sessionName, makeSession(sessionName, androidDevice)); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['location', 'true'], input: { app: 'com.example.app' }, @@ -627,8 +627,8 @@ test('settings Android location 0 refuses a named app before reaching the owner' sessionStore.publish(sessionName, makeSession(sessionName, androidDevice)); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['location', '0'], input: { app: 'com.example.app' }, @@ -654,8 +654,8 @@ test('settings iOS location set refuses a named app before reaching the owner', sessionStore.publish(sessionName, makeSession(sessionName, iosSimulatorDevice)); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['location', 'set', '37.7', '-122.4'], input: { app: 'com.example.app' }, @@ -682,8 +682,8 @@ test('settings macOS permission grant refuses a named app before reaching the ow sessionStore.publish(sessionName, makeSession(sessionName, macOsDevice)); const response = await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: snapshotRequest(sessionName, 'settings', { positionals: ['permission', 'grant', 'screen-recording'], input: { app: 'com.example.app' }, diff --git a/src/daemon/handlers/snapshot-runtime-route.test.ts b/src/daemon/handlers/snapshot-runtime-route.test.ts index 582927b10e..5a1a914404 100644 --- a/src/daemon/handlers/snapshot-runtime-route.test.ts +++ b/src/daemon/handlers/snapshot-runtime-route.test.ts @@ -39,8 +39,8 @@ test('snapshot route forwards the request runtime facts and binding seams exactl const platformServices = daemonPlatformServicesFixture(); await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices, + createCommandSurface: testCreateCommandSurface, + platformServices, req, sessionName: 'snapshot-route', logPath: '/tmp/snapshot-route.log', @@ -76,8 +76,8 @@ test('diff route forwards the request runtime facts and binding seams exactly on const platformServices = daemonPlatformServicesFixture(); await handleSnapshotCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices, + createCommandSurface: testCreateCommandSurface, + platformServices, req, sessionName: 'diff-route', logPath: '/tmp/diff-route.log', diff --git a/src/daemon/interaction/index.ts b/src/daemon/interaction/index.ts index 2277627428..4f66f480f8 100644 --- a/src/daemon/interaction/index.ts +++ b/src/daemon/interaction/index.ts @@ -66,14 +66,14 @@ export const captureSnapshotForSession: CaptureSnapshotForSession = async ( }); }; -export function createInteractionRuntime( +export async function createInteractionRuntime( params: InteractionRouteInput & { pairedGestureViewport?: Rect; touchExecutor?: import('../touch-runtime.ts').BoundTouchExecutor; gestures?: import('../gesture-runtime.ts').BoundGestureExecutor; }, ) { - return createInteractionRuntimeForRoute({ + return await createInteractionRuntimeForRoute({ ...bindInteractionSession(params), captureSnapshotForSession: params.captureSnapshotForSession ?? captureSnapshotForSession, }); diff --git a/src/daemon/interaction/internal/__tests__/interaction-dispatch-disclosure.test.ts b/src/daemon/interaction/internal/__tests__/interaction-dispatch-disclosure.test.ts index faf5b4e9b0..88e13b7413 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-dispatch-disclosure.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-dispatch-disclosure.test.ts @@ -96,8 +96,8 @@ async function press({ }; sessionStore.publish(session.name, session); const response = await routeInteraction({ - - createCommandSurface: testCreateCommandSurface,req: { token: 't', session: session.name, command, positionals, flags }, + createCommandSurface: testCreateCommandSurface, + req: { token: 't', session: session.name, command, positionals, flags }, sessionName: session.name, sessionStore, contextFromFlags, @@ -194,8 +194,8 @@ async function swipeRefusedOnSecondRepetition(): Promise { const session = makeSession('dispatch-disclosure-swipe'); sessionStore.publish(session.name, session); const response = await routeInteraction({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: session.name, @@ -228,8 +228,8 @@ async function gestureDragRefusedBeforeDispatch(): Promise { expireRefFrame(session); sessionStore.publish(session.name, session); const response = await routeInteraction({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: session.name, @@ -287,8 +287,8 @@ async function refRefusedAfterAndroidRecovery(command: 'press' | 'gesture'): Pro openApp, }; const response = await routeInteraction({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: session.name, @@ -344,8 +344,8 @@ async function pressThenForegroundReadRefused(): Promise { }, }; const response = await routeInteraction({ - - createCommandSurface: testCreateCommandSurface,req: { token: 't', session: session.name, command: 'press', positionals: ['50', '40'] }, + createCommandSurface: testCreateCommandSurface, + req: { token: 't', session: session.name, command: 'press', positionals: ['50', '40'] }, sessionName: session.name, sessionStore, contextFromFlags, @@ -455,8 +455,8 @@ test('a plain Error a backend throws reaches the wire with dispatched unknown', }) as unknown as typeof bindings.bindDevice; await assert.rejects( routeInteraction({ - - createCommandSurface: testCreateCommandSurface,req: { token: 't', session: session.name, command: 'press', positionals: ['@e1'], flags: {} }, + createCommandSurface: testCreateCommandSurface, + req: { token: 't', session: session.name, command: 'press', positionals: ['@e1'], flags: {} }, sessionName: session.name, sessionStore, contextFromFlags, diff --git a/src/daemon/interaction/internal/__tests__/interaction-gesture-drag.test.ts b/src/daemon/interaction/internal/__tests__/interaction-gesture-drag.test.ts index 3a2e1675f2..09ee190d9f 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-gesture-drag.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-gesture-drag.test.ts @@ -63,8 +63,8 @@ function makeDragSession(sessionName: string) { async function runDrag(sessionStore: ReturnType, sessionName: string) { return await handleInteractionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, diff --git a/src/daemon/interaction/internal/__tests__/interaction-settle.test.ts b/src/daemon/interaction/internal/__tests__/interaction-settle.test.ts index 18eddbcf67..9018a2ef7f 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-settle.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-settle.test.ts @@ -113,8 +113,8 @@ test('interaction runtime inherits the registered daemon request signal', async try { const runtime = await createInteractionRuntime({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -218,8 +218,8 @@ test('press --settle responds with the settled diff, refsGeneration, and activat mockCommandDispatch({ snapshots: [BEFORE_NODES, AFTER_NODES, AFTER_NODES, AFTER_NODES] }); const response = await handleInteractionCommands({ - - createCommandSurface: testCreateCommandSurface,req: { + createCommandSurface: testCreateCommandSurface, + req: { token: 't', session: sessionName, command: 'press', @@ -281,8 +281,8 @@ test('held touch settle publishes refs into the current record of its scoped lif }; }); const running = handleInteractionCommands({ - - createCommandSurface: testCreateCommandSurface,req: { + createCommandSurface: testCreateCommandSurface, + req: { token: 't', session: address, command: 'press', @@ -356,8 +356,8 @@ test('press --settle on a removals-only diff attaches the unchanged interactive }); const response = await handleInteractionCommands({ - - createCommandSurface: testCreateCommandSurface,req: { + createCommandSurface: testCreateCommandSurface, + req: { token: 't', session: sessionName, command: 'press', @@ -393,8 +393,8 @@ test('press --settle rejects an expired-frame ref before dispatch or observation ); const response = await handleInteractionCommands({ - - createCommandSurface: testCreateCommandSurface,req: { + createCommandSurface: testCreateCommandSurface, + req: { token: 't', session: sessionName, command: 'press', @@ -435,8 +435,8 @@ test('a settle observation without a diff leaves ref staleness untouched', async }); const response = await handleInteractionCommands({ - - createCommandSurface: testCreateCommandSurface,req: { + createCommandSurface: testCreateCommandSurface, + req: { token: 't', session: sessionName, command: 'press', @@ -501,8 +501,8 @@ test('a stalled settle capture receives its deadline signal and leaves the inter const startedAt = Date.now(); const response = await handleInteractionCommands({ - - createCommandSurface: testCreateCommandSurface,req: { + createCommandSurface: testCreateCommandSurface, + req: { token: 't', session: sessionName, command: 'press', @@ -532,8 +532,8 @@ test('bare timeout without --settle stays compatible', async () => { mockCommandDispatch({ snapshots: [BEFORE_NODES] }); const compatible = await handleInteractionCommands({ - - createCommandSurface: testCreateCommandSurface,req: { + createCommandSurface: testCreateCommandSurface, + req: { token: 't', session: sessionName, command: 'press', @@ -558,8 +558,8 @@ test('settle-specific tuning flags without --settle are rejected', async () => { mockCommandDispatch({ snapshots: [BEFORE_NODES] }); const response = await handleInteractionCommands({ - - createCommandSurface: testCreateCommandSurface,req: { + createCommandSurface: testCreateCommandSurface, + req: { token: 't', session: sessionName, command: 'press', @@ -584,8 +584,8 @@ test('fill @ref --settle carries the settle payload on the ref wire shape', asyn mockCommandDispatch({ snapshots: [AFTER_NODES, AFTER_NODES] }); const response = await handleInteractionCommands({ - - createCommandSurface: testCreateCommandSurface,req: { + createCommandSurface: testCreateCommandSurface, + req: { token: 't', session: sessionName, command: 'fill', diff --git a/src/daemon/interaction/internal/interaction-gesture.ts b/src/daemon/interaction/internal/interaction-gesture.ts index 782c22bf45..7e253533ab 100644 --- a/src/daemon/interaction/internal/interaction-gesture.ts +++ b/src/daemon/interaction/internal/interaction-gesture.ts @@ -49,7 +49,7 @@ type GestureHandlerParams = InteractionRouteInput & { captureSnapshotForSession: CaptureSnapshotForSession; }; -type GestureRuntime = ReturnType; +type GestureRuntime = Awaited>; type GestureRuntimeResult = Awaited>; type GestureInteractionOutcome = { @@ -102,7 +102,7 @@ async function runGestureInteraction( bindDevice: params.bindDevice, }); if (!bound.ok) return { refused: bound.response }; - const runtime = createGestureRuntime(params, bound.gestures); + const runtime = await createGestureRuntime(params, bound.gestures); const context = { session: params.sessionName, requestId: params.req.meta?.requestId }; const result = await runPreparedGesture(runtime, context, gesture, params.req.internal); return buildGestureOutcome(input, gesture, result, params.req.flags); @@ -180,7 +180,7 @@ export async function dispatchSwipeViaRuntime( const count = input.count ?? 1; const pauseMs = input.pauseMs ?? 0; const pattern = input.pattern ?? 'one-way'; - const runtime = createGestureRuntime(params, bound.gestures); + const runtime = await createGestureRuntime(params, bound.gestures); const result = await runSwipeRepetitions(runtime, params, input, count, pauseMs, pattern); return { positionals: swipeReplayPositionals(input), @@ -203,7 +203,10 @@ export async function dispatchSwipeViaRuntime( }); } -function createGestureRuntime(params: GestureHandlerParams, gestures: BoundGestureExecutor) { +function createGestureRuntime( + params: GestureHandlerParams, + gestures: BoundGestureExecutor, +): Promise { return createInteractionRuntimeForRoute({ ...params, gestures, @@ -391,7 +394,7 @@ function swipeReplayPositionals(input: SwipePayload): string[] { } async function runSwipeRepetitions( - runtime: ReturnType, + runtime: GestureRuntime, params: InteractionRouteInput, input: SwipePayload, count: number, diff --git a/src/daemon/interaction/internal/interaction-runtime.ts b/src/daemon/interaction/internal/interaction-runtime.ts index 52e65e703c..1f578f334f 100644 --- a/src/daemon/interaction/internal/interaction-runtime.ts +++ b/src/daemon/interaction/internal/interaction-runtime.ts @@ -33,7 +33,7 @@ import type { BoundTouchExecutor } from '../../touch-runtime.ts'; import type { BoundGestureExecutor } from '../../gesture-runtime.ts'; import { NO_ACTIVE_SESSION_MESSAGE } from '@agent-device/kernel/contracts'; -export function createInteractionRuntimeForRoute( +export async function createInteractionRuntimeForRoute( params: InteractionRouteInput & { captureSnapshotForSession: CaptureSnapshotForSession; pairedGestureViewport?: Rect; @@ -52,7 +52,7 @@ export function createInteractionRuntimeForRoute( const ref = bindInteractionSession(params).sessionRef; if (!ref) throw new KernelAppError('SESSION_NOT_FOUND', NO_ACTIVE_SESSION_MESSAGE); const session = params.sessionStore.requireCurrent(ref); - return createInteractionAgentDevice({ + return await createInteractionAgentDevice({ createCommandSurface: params.createCommandSurface, requestId: params.req.meta?.requestId, flags: params.req.flags, diff --git a/src/daemon/interaction/internal/interaction-touch-press.ts b/src/daemon/interaction/internal/interaction-touch-press.ts index 33779fdba0..6c6f26588d 100644 --- a/src/daemon/interaction/internal/interaction-touch-press.ts +++ b/src/daemon/interaction/internal/interaction-touch-press.ts @@ -153,7 +153,7 @@ function buildTargetedRuntimeOptions( } async function runTargetedTouchInteraction(params: { - runtime: ReturnType; + runtime: Awaited>; command: TargetedTouchCommand; target: InteractionTarget; sessionName: string; diff --git a/src/daemon/interaction/internal/interaction-touch-runtime.ts b/src/daemon/interaction/internal/interaction-touch-runtime.ts index 028163a399..36ec2a12e5 100644 --- a/src/daemon/interaction/internal/interaction-touch-runtime.ts +++ b/src/daemon/interaction/internal/interaction-touch-runtime.ts @@ -60,7 +60,7 @@ export async function dispatchRuntimeInteraction< target: InteractionTarget; extra: Record; }; - run(runtime: ReturnType): Promise; + run(runtime: Awaited>): Promise; /** May return a warning to append to the successful response (e.g. a pending Android permission dialog). */ afterRun?(result: TResult): Promise; buildPayloads( @@ -72,7 +72,7 @@ export async function dispatchRuntimeInteraction< if (!params.sessionRef) return noActiveSessionError(); const session = params.sessionStore.requireCurrent(params.sessionRef); const consumedCapture: { state?: SnapshotState } = {}; - const runtime = createInteractionRuntimeForRoute({ + const runtime = await createInteractionRuntimeForRoute({ ...params, touchExecutor: options.touchExecutor, consumedCapture, diff --git a/src/daemon/screenshot-runtime.ts b/src/daemon/screenshot-runtime.ts index bb94bd2f45..500630360f 100644 --- a/src/daemon/screenshot-runtime.ts +++ b/src/daemon/screenshot-runtime.ts @@ -120,7 +120,7 @@ export async function captureScreenshotArtifact( }>, ): Promise { const { session, sessionName, outPath, dispatchContext } = params; - const runtime = params.createCommandSurface({ + const runtime = await params.createCommandSurface({ backend: createBoundScreenshotBackend(params), artifacts: createDaemonScreenshotArtifactAdapter(), sessions: createReadonlyRuntimeSessionStore(sessionName, session), diff --git a/src/daemon/selector-runtime-backend.test.ts b/src/daemon/selector-runtime-backend.test.ts index 352089d0d9..f2fdf22a56 100644 --- a/src/daemon/selector-runtime-backend.test.ts +++ b/src/daemon/selector-runtime-backend.test.ts @@ -54,8 +54,8 @@ test('wait text passes its poll deadline signal to the Apple runner fast path', }), ); const runtime = await createSelectorRuntimeForDevice({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), ref: sessionStore.lookup(sessionName), req: { token: 't', @@ -117,8 +117,8 @@ test('daemon wait stable pins private-ax on emitted snapshot runner requests', a }, })); const runtime = await createSelectorRuntimeForDevice({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), ref: sessionStore.lookup(sessionName), req: { token: 't', diff --git a/src/daemon/selector-runtime-backend.ts b/src/daemon/selector-runtime-backend.ts index db8900a4fa..482d1bedf7 100644 --- a/src/daemon/selector-runtime-backend.ts +++ b/src/daemon/selector-runtime-backend.ts @@ -71,7 +71,7 @@ type ResolvedSelectorRuntime = | { ok: true; ref: SessionRef | undefined; - runtime: ReturnType; + runtime: Awaited>; } | { ok: false; response: DaemonResponse }; @@ -84,8 +84,8 @@ type ResolvedSelectorDevice = } | { ok: false; response: DaemonResponse }; -export function createSelectorRuntimeForDevice(params: SelectorRuntimeDeviceParams) { - return params.createCommandSurface({ +export async function createSelectorRuntimeForDevice(params: SelectorRuntimeDeviceParams) { + return await params.createCommandSurface({ backend: createSelectorBackend(params), ...createDaemonRuntimePolicy('selector commands', { plural: true }), sessions: createDaemonRuntimeSessionStore({ @@ -147,7 +147,7 @@ export async function createBoundSelectorRuntime( return { ok: true, ref: resolved.ref, - runtime: createSelectorRuntimeForDevice({ + runtime: await createSelectorRuntimeForDevice({ ...params, ref: resolved.ref, session: resolved.session, diff --git a/src/daemon/server/daemon-runtime-idle-reap.test.ts b/src/daemon/server/daemon-runtime-idle-reap.test.ts index 99e5c65b6e..e361cdd698 100644 --- a/src/daemon/server/daemon-runtime-idle-reap.test.ts +++ b/src/daemon/server/daemon-runtime-idle-reap.test.ts @@ -38,7 +38,7 @@ test('daemon runtime self-reaps after the idle window when nothing ever uses it' try { const runtime = await startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -78,7 +78,7 @@ test('daemon runtime never self-reaps when AGENT_DEVICE_DAEMON_IDLE_TIMEOUT_MS i try { const runtime = await startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -116,7 +116,7 @@ test('only a retained lease keeps the daemon runtime from self-reaping', async ( try { const runtime = await startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, diff --git a/src/daemon/server/daemon-runtime-interactor-composition.test.ts b/src/daemon/server/daemon-runtime-interactor-composition.test.ts index 02e6121ac1..6a1cf2180d 100644 --- a/src/daemon/server/daemon-runtime-interactor-composition.test.ts +++ b/src/daemon/server/daemon-runtime-interactor-composition.test.ts @@ -61,7 +61,7 @@ test('daemon startup composes the interactor resolution the daemon resolves thro const providerInteractor = { snapshot: async () => ({ nodes: [] }) } as unknown as Interactor; try { const runtime = await startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -96,7 +96,7 @@ test('daemon startup compares lease credentials with its own startup environment const stateDir = mkdtempForTestSync('agent-device-daemon-credential-composition-'); const daemonEnv = { BROWSERSTACK_USERNAME: 'user', BROWSERSTACK_ACCESS_KEY: 'key-1' }; const runtime = await startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { ...process.env, ...daemonEnv, @@ -163,7 +163,7 @@ test('a daemon attempt losing the lock shuts down every constructed provider', a }); const exit = vi.fn(); const runtime = await startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { AGENT_DEVICE_STATE_DIR: stateDir }, exit, registerProcessHandlers: false, diff --git a/src/daemon/server/daemon-runtime-lifecycle-shutdown.test.ts b/src/daemon/server/daemon-runtime-lifecycle-shutdown.test.ts index 6a79f161fd..111af1a5b9 100644 --- a/src/daemon/server/daemon-runtime-lifecycle-shutdown.test.ts +++ b/src/daemon/server/daemon-runtime-lifecycle-shutdown.test.ts @@ -155,7 +155,7 @@ test('daemon shutdown detaches before session teardown and force-finalizes only const startupErrors: string[] = []; try { const runtime = await startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -184,7 +184,7 @@ test('a SIGTERM shutdown gives the handoff a diagnostics scope to write its reas const stateDir = mkdtempForTestSync('agent-device-daemon-detach-diagnostics-'); try { const runtime = await startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -213,7 +213,7 @@ const claimStores = isolatedDeviceClaimStores('daemon-drain-admission-'); test('shutdown includes drain publications, releases their claims and refuses post-snapshot publication', async () => { const { stateDir } = claimStores(); const runtime = await startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -268,7 +268,7 @@ test('shutdown includes drain publications, releases their claims and refuses po test('shutdown joins dispatch completion after force-closing the client before taking its snapshot', async () => { const { stateDir } = claimStores(); const runtime = await startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -407,7 +407,7 @@ test.each([false, true])( test('daemon shutdown releases a session lease whose session teardown rejects', async () => { const stateDir = mkdtempForTestSync('agent-device-daemon-session-lease-shutdown-'); const runtime = await startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, @@ -442,7 +442,7 @@ test('daemon shutdown releases a retainOnClose lease that no session holds', asy const stateDir = mkdtempForTestSync('agent-device-daemon-retained-lease-shutdown-'); try { const runtime = await startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, diff --git a/src/daemon/server/daemon-runtime-metadata-ownership.test.ts b/src/daemon/server/daemon-runtime-metadata-ownership.test.ts index df91edadb3..a35a90e9aa 100644 --- a/src/daemon/server/daemon-runtime-metadata-ownership.test.ts +++ b/src/daemon/server/daemon-runtime-metadata-ownership.test.ts @@ -69,8 +69,8 @@ const SUCCESSOR_PID = 999_999_999; function startRuntime(stateDir: string, exit: (code: number) => void) { return startDaemonRuntime({ - - createCommandSurface: testCreateCommandSurface,env: { + createCommandSurface: testCreateCommandSurface, + env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, AGENT_DEVICE_DAEMON_IDLE_TIMEOUT_MS: '0', diff --git a/src/daemon/server/daemon-runtime-recording-reaper.test.ts b/src/daemon/server/daemon-runtime-recording-reaper.test.ts index a5560317f4..c64fde7db4 100644 --- a/src/daemon/server/daemon-runtime-recording-reaper.test.ts +++ b/src/daemon/server/daemon-runtime-recording-reaper.test.ts @@ -53,7 +53,7 @@ test('daemon startup reaps orphaned simctl recorders with a graceful finalize wi const stateDir = mkdtempForTestSync('agent-device-daemon-recording-reaper-'); try { const runtime = await startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, diff --git a/src/daemon/server/daemon-runtime-xctest-device-set.test.ts b/src/daemon/server/daemon-runtime-xctest-device-set.test.ts index 6d0e4d644f..22cbc09d45 100644 --- a/src/daemon/server/daemon-runtime-xctest-device-set.test.ts +++ b/src/daemon/server/daemon-runtime-xctest-device-set.test.ts @@ -75,7 +75,7 @@ function loggedEvents(logPath: string): Array<{ phase: string; data?: Record startDaemonRuntime({ - loadCommandSurface: async () => testCreateCommandSurface, + createCommandSurface: testCreateCommandSurface, env: { ...process.env, AGENT_DEVICE_STATE_DIR: stateDir, diff --git a/src/daemon/server/daemon-runtime.ts b/src/daemon/server/daemon-runtime.ts index 4529ec02e0..6bde5986a1 100644 --- a/src/daemon/server/daemon-runtime.ts +++ b/src/daemon/server/daemon-runtime.ts @@ -18,7 +18,7 @@ import { loadDaemonPolicy, type DaemonPolicy, } from '@agent-device/daemon-contracts/daemon-policy-file'; -import type { LoadDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; +import type { CreateDaemonCommandSurface } from '@agent-device/contracts/daemon-command-surface'; import { getInteractor } from '../../core/interactors.ts'; import { installInteractorResolution } from '../interactor-resolution.ts'; import { @@ -212,12 +212,8 @@ export type DaemonRuntimeOptions = { stderr?: WritableOutput; exit?: (code: number) => void; registerProcessHandlers?: boolean; - /** - * Loads the command-surface factory the request handler builds per-request runtimes from. - * Required, and awaited once at startup: the composition root keeps the surface behind - * `await import` so it never joins `src/daemon.ts`'s eager closure (ADR 0019). - */ - loadCommandSurface: LoadDaemonCommandSurface; + /** The command-surface factory the request handler builds per-request runtimes from. */ + createCommandSurface: CreateDaemonCommandSurface; }; export type DaemonRuntimeController = { @@ -335,9 +331,6 @@ export async function startDaemonRuntime( const daemonPaths = resolveDaemonPaths(env.AGENT_DEVICE_STATE_DIR); const { baseDir, infoPath, logPath, sessionsDir } = daemonPaths; const daemonServerMode = resolveDaemonServerMode(env.AGENT_DEVICE_DAEMON_SERVER_MODE); - // The composition root's loader resolves once here; the request handler keeps threading the - // sync factory it returns, so the `await import` never enters this module's import time. - const createCommandSurface = await options.loadCommandSurface(); const retainArtifacts = isEnvTruthy(env.AGENT_DEVICE_RETAIN_ARTIFACTS); // ADR 0029: a policy that cannot be read or validated stops startup; the daemon never runs // with a weaker policy than its operator named. @@ -461,7 +454,7 @@ export async function startDaemonRuntime( providerDeviceRuntimeScope: providerRuntimeProviders.providerDeviceRuntimeScope, trackDownloadableArtifact, daemonPolicy, - createCommandSurface, + createCommandSurface: options.createCommandSurface, }); let stopMetadataLossWatch: () => void = () => {}; diff --git a/src/daemon/session-lifecycle/__tests__/application.test.ts b/src/daemon/session-lifecycle/__tests__/application.test.ts index d344552ae7..d885605111 100644 --- a/src/daemon/session-lifecycle/__tests__/application.test.ts +++ b/src/daemon/session-lifecycle/__tests__/application.test.ts @@ -52,8 +52,8 @@ function request(command: DaemonRequest['command']): DaemonRequest { async function run(command: DaemonRequest['command']): Promise { return await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: request(command), sessionName: 'default', logPath: '/tmp/agent-device-session-lifecycle-route.log', diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-capabilities-install-projection.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-capabilities-install-projection.test.ts index e0460aa683..0d0ba65332 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-capabilities-install-projection.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-capabilities-install-projection.test.ts @@ -155,8 +155,8 @@ async function dispatchCapabilities(params: { inspectFacts: InspectDeviceRuntimeFacts; }) { return await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: params.sessionName, diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-capabilities.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-capabilities.test.ts index 5c10a0f7ad..9fac4de244 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-capabilities.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-capabilities.test.ts @@ -62,8 +62,8 @@ async function projectAndroidCapabilities(sessionName: string) { providerMode: 'local', }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -146,8 +146,8 @@ test('capabilities excludes logs from an unavailable provider-mode XCTest runtim }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -191,8 +191,8 @@ test('capabilities excludes network when the runtime fact is unavailable', async }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -237,8 +237,8 @@ test('capabilities includes apps for the available HarmonyOS runtime fact', asyn async (request) => (request.platform === 'harmonyos' ? [harmonyDevice] : []), async () => await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -317,8 +317,8 @@ test.each(APPS_UNAVAILABLE_CAPABILITY_CASES)( }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -353,8 +353,8 @@ test('capabilities excludes appstate when its runtime fact is unavailable', asyn }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -388,8 +388,8 @@ test('capabilities excludes appstate when its readiness fact is unavailable', as }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -465,8 +465,8 @@ test.each([ async () => [device], async () => await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -503,8 +503,8 @@ test('capabilities accepts a stopped Android AVD placeholder for explicit platfo async (request) => (request.platform === 'android' ? [stoppedAvd] : []), async () => await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-close-cwd-scoped-resources.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-close-cwd-scoped-resources.test.ts index 416a578ad6..b0aa6d6c41 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-close-cwd-scoped-resources.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-close-cwd-scoped-resources.test.ts @@ -53,8 +53,8 @@ const IOS_SIM: SessionState['device'] = { async function closeAtAddress(sessionStore: ReturnType) { return await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: ADDRESS, command: 'close', positionals: [], flags: {} }, sessionName: ADDRESS, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-close-error-precedence.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-close-error-precedence.test.ts index 7cf12fce8b..34a798add2 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-close-error-precedence.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-close-error-precedence.test.ts @@ -50,8 +50,8 @@ test('targeted close preserves the platform-close AppError and still runs later await expect( handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -130,8 +130,8 @@ test('a failed platform close retains the device claim and reports it', async () let caught: unknown; try { await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -245,8 +245,8 @@ test('a failing best-effort cleanup also retains the device claim and reports it let caught: unknown; try { await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -332,8 +332,8 @@ test('a successful close clears the device claim', async () => { sessionStore.publish(sessionName, session); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -386,8 +386,8 @@ test('targeted close skips platform dispatch and preserves the error when the re await expect( handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-close-lifecycle-runtime.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-close-lifecycle-runtime.test.ts index 65390b2ba1..e3fc25ad93 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-close-lifecycle-runtime.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-close-lifecycle-runtime.test.ts @@ -65,8 +65,8 @@ async function close(params: { internal?: DaemonRequest['internal']; }) { return await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: closeRequest(params.sessionName, params.positionals ?? [], params.internal), sessionName: params.sessionName, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-close-resource-cleanup.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-close-resource-cleanup.test.ts index c434c9cf42..1de1521af0 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-close-resource-cleanup.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-close-resource-cleanup.test.ts @@ -44,8 +44,8 @@ test('close stops Android snapshot helper session before deleting session', asyn }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -141,8 +141,8 @@ test('close stops active host audio probe before deleting session', async () => ); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -167,8 +167,8 @@ test('close dispatches web session cleanup without a positional target', async ( sessionStore.publish(sessionName, makeSession(sessionName, WEB_DESKTOP_DEVICE)); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -300,8 +300,8 @@ test('close cannot retire a replacement session while its provider release waits return { releasedBy: 'provider' }; }); const closing = handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: address, command: 'close', positionals: [], flags: {} }, sessionName: address, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-close-save-script.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-close-save-script.test.ts index 174cc1389e..8fb38e5009 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-close-save-script.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-close-save-script.test.ts @@ -38,8 +38,8 @@ test('close --save-script on a never-armed session is rejected before teardown, await expect( handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -74,8 +74,8 @@ test('close --save-script on a never-armed session is rejected before teardown, // A plain close (no --save-script) still closes the same session cleanly afterward, and now // teardown genuinely does run: the recorder is signaled and the session deleted. const plainClose = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -115,8 +115,8 @@ test('close --save-script on a session with an active .ad repair transaction is sessionStore.publish(sessionName, session); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-close-shutdown-platform.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-close-shutdown-platform.test.ts index b08dfad60f..637b1a2500 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-close-shutdown-platform.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-close-shutdown-platform.test.ts @@ -73,8 +73,8 @@ test('close --shutdown calls shutdownSimulator for iOS simulator and includes re }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -140,8 +140,8 @@ test('close --shutdown keeps a selected provider-owned iOS simulator off local s }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -184,8 +184,8 @@ test('close --shutdown calls shutdownAndroidEmulator for Android emulator and in }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -229,8 +229,8 @@ test('close --shutdown is ignored for non-simulator iOS devices', async () => { ); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -269,8 +269,8 @@ test('close --shutdown is ignored for Android devices', async () => { ); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -317,8 +317,8 @@ test('close --shutdown returns success and failure payload when shutdownAndroidE }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -359,8 +359,8 @@ test('close --shutdown returns success and failure payload when shutdownSimulato }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-devices-batch-runtime.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-devices-batch-runtime.test.ts index 33a16657bb..4a0bc32b73 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-devices-batch-runtime.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-devices-batch-runtime.test.ts @@ -390,8 +390,8 @@ test('close clears retained materialized install paths bound to the session', as mockCleanupRetainedMaterializedPaths.mockImplementation(realCleanup); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, command: 'close', positionals: [], flags: {} }, sessionName, logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-open-execution-runtime.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-open-execution-runtime.test.ts index a9b89fe4e8..228a38d4e8 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-open-execution-runtime.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-open-execution-runtime.test.ts @@ -132,8 +132,8 @@ test('open runtime payload replaces stored session runtime atomically', async () }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -213,8 +213,8 @@ test('open runtime payload clears stale applied transport hints before launch', }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -266,8 +266,8 @@ test('open runtime payload rejects invalid metro port before app launch', async }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'runtime-open-invalid-port', @@ -308,8 +308,8 @@ test('open runtime payload rejects malformed runtime objects without mutating se mockResolveTargetDevice.mockResolvedValue(makeAndroidEmulator()); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -353,8 +353,8 @@ test('open runtime payload does not persist replacement when launch fails', asyn await expect( handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -396,8 +396,8 @@ test('a first open keeps both positionals so the deep link still reaches the app }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'deep-link-open', @@ -442,8 +442,8 @@ test('open reports the launch confirmation its platform answered', async () => { }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'launch-confirmation-open', @@ -485,8 +485,8 @@ async function openReportingObservation( }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -555,8 +555,8 @@ function invokeHeldOpen( req?: Partial, ) { return handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', command: 'open', diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-open-existing.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-open-existing.test.ts index 6d3d2caa49..fb42f103ed 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-open-existing.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-open-existing.test.ts @@ -40,8 +40,8 @@ test('open web URL on iOS device session without active app falls back to Safari }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -92,8 +92,8 @@ test('open web URL on iOS simulator session without active app falls back to Saf }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -139,8 +139,8 @@ test('open app and URL on existing iOS device session keeps app context', async }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -188,8 +188,8 @@ test('open app on existing macOS session resolves and stores bundle id', async ( }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -222,8 +222,8 @@ test('open rejects --surface on non-macOS devices', async () => { }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'ios-surface', @@ -275,8 +275,8 @@ test('open on existing macOS frontmost-app session preserves surface without --s try { const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -345,8 +345,8 @@ test('open on existing iOS session refreshes unavailable simulator by name', asy const response = await withMockedPlatform('darwin', async () => handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -396,8 +396,8 @@ test('open app on existing Android session resolves and stores package id', asyn mockResolveAndroidPackage.mockResolvedValue('org.reactjs.native.example.RNCLI83'); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -442,8 +442,8 @@ test('open intent target on existing Android session clears stale package contex mockResolveAndroidPackage.mockResolvedValue(undefined); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -497,8 +497,8 @@ test('open on existing Android session preserves a comparable freshness baseline mockResolveAndroidPackage.mockResolvedValue('com.android.settings'); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-open-foreground.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-open-foreground.test.ts index 424e9e058d..e0421d1beb 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-open-foreground.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-open-foreground.test.ts @@ -152,8 +152,8 @@ const failedOpenResponse: DaemonResponse = { test('passes a failed open response through untouched', async () => { const result = await composeOpenWithInitialSnapshot({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: baseRequest({ flags: { foreground: true } }), ref, logPath: '/tmp/daemon.log', @@ -169,8 +169,8 @@ test('passes a failed open response through untouched', async () => { test('leaves a successful open response untouched when --foreground was not requested', async () => { const result = await composeOpenWithInitialSnapshot({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: baseRequest(), ref, logPath: '/tmp/daemon.log', @@ -189,8 +189,8 @@ test('attaches the initial INTERACTIVE snapshot by delegating to the existing sn const req = baseRequest({ flags: { foreground: true }, positionals: ['xyz.blueskyweb.app'] }); const result = await composeOpenWithInitialSnapshot({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req, ref, logPath: '/tmp/daemon.log', @@ -243,8 +243,8 @@ test('a snapshot-capture failure never masks the successful open', async () => { dispatchSnapshotViaRuntime.mockResolvedValue(snapshotFailure); const result = await composeOpenWithInitialSnapshot({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: baseRequest({ flags: { foreground: true } }), ref, logPath: '/tmp/daemon.log', @@ -287,8 +287,8 @@ test('a THROWN snapshot-capture failure never masks the successful open either', ); const result = await composeOpenWithInitialSnapshot({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: baseRequest({ flags: { foreground: true } }), ref, logPath: '/tmp/daemon.log', diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-open-runtime.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-open-runtime.test.ts index bc003a3784..2fd4331600 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-open-runtime.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-open-runtime.test.ts @@ -125,8 +125,8 @@ test('open applies stored runtime launchUrl and reports runtime hints', async () }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'runtime-open', @@ -190,8 +190,8 @@ test('open rejects a false runtime-hints fact before its one implementation bind }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'runtime-open-false-fact', @@ -243,8 +243,8 @@ test('open applies launch-only flags only to the direct app launch before runtim }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'launch-console-runtime', @@ -287,8 +287,8 @@ test('open --metro-port alone defaults the host to 10.0.2.2 on an Android emulat }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -320,8 +320,8 @@ test('open --metro-port alone defaults the host to 127.0.0.1 on an iOS simulator }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -353,8 +353,8 @@ test('open --metro-port alone stays host-ambiguous on a physical Android device' }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -386,8 +386,8 @@ test('open --relaunch allows Android package names ending with apk-like suffix', }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'default', diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-teardown-resources.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-teardown-resources.test.ts index dabbeedf9f..e0f53c05af 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-teardown-resources.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-teardown-resources.test.ts @@ -43,8 +43,8 @@ test('close finalizes an active iOS simulator recording before deleting the sess sessionStore.publish(sessionName, session); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -83,8 +83,8 @@ test('close surfaces a recording finalization failure through the cleanup-failur await expect( handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, diff --git a/src/daemon/session-observability/__tests__/application.test.ts b/src/daemon/session-observability/__tests__/application.test.ts index 17d7b5cb19..c041027be0 100644 --- a/src/daemon/session-observability/__tests__/application.test.ts +++ b/src/daemon/session-observability/__tests__/application.test.ts @@ -33,8 +33,8 @@ function request(command: DaemonRequest['command']): DaemonRequest { async function run(command: DaemonRequest['command']): Promise { return await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: request(command), sessionName: 'default', logPath: '/tmp/agent-device-session-observability-route.log', diff --git a/src/daemon/session-observability/internal/__tests__/session-logs.test.ts b/src/daemon/session-observability/internal/__tests__/session-logs.test.ts index 6d18339d31..bfce70e94e 100644 --- a/src/daemon/session-observability/internal/__tests__/session-logs.test.ts +++ b/src/daemon/session-observability/internal/__tests__/session-logs.test.ts @@ -298,8 +298,8 @@ async function runLogs( throwIfCanceled?: () => void, ) { return await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, command: 'logs', positionals, flags }, sessionName, logPath: '/tmp/daemon.log', diff --git a/src/daemon/session-observability/internal/__tests__/session-network-runtime-parity.test.ts b/src/daemon/session-observability/internal/__tests__/session-network-runtime-parity.test.ts index a8ff65f847..ed73795f27 100644 --- a/src/daemon/session-observability/internal/__tests__/session-network-runtime-parity.test.ts +++ b/src/daemon/session-observability/internal/__tests__/session-network-runtime-parity.test.ts @@ -61,8 +61,8 @@ test.each(NETWORK_RUNTIME_PROJECTION_PARITY)( const runtime = createNetworkRuntime(session.device, async () => result); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, @@ -116,8 +116,8 @@ test('network admission remains fail-closed before parsing invalid input', async }); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: session.name, @@ -215,8 +215,8 @@ async function runNetwork( bindDevice: BindDeviceRuntime, ) { return await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: sessionName, diff --git a/src/daemon/session-observability/internal/__tests__/session-network.test.ts b/src/daemon/session-observability/internal/__tests__/session-network.test.ts index d83885cb8c..78d1407223 100644 --- a/src/daemon/session-observability/internal/__tests__/session-network.test.ts +++ b/src/daemon/session-observability/internal/__tests__/session-network.test.ts @@ -14,8 +14,8 @@ test('network requires an active session before requesting a runtime binding', a const sessionStore = makeSessionStore(); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: 'missing', @@ -49,8 +49,8 @@ test('network validates the legacy limit after runtime-fact admission', async () ); const response = await handleSessionCommands({ - - createCommandSurface: testCreateCommandSurface,platformServices: daemonPlatformServicesFixture(), + createCommandSurface: testCreateCommandSurface, + platformServices: daemonPlatformServicesFixture(), req: { token: 't', session: session.name, diff --git a/src/daemon/snapshot-command-runtime.ts b/src/daemon/snapshot-command-runtime.ts index c48ab74efa..f65d11d8e6 100644 --- a/src/daemon/snapshot-command-runtime.ts +++ b/src/daemon/snapshot-command-runtime.ts @@ -44,7 +44,7 @@ export type SnapshotRuntimeRecord = type SnapshotRuntimeCommandParams = SnapshotRuntimeRouteParams & { command: 'snapshot' | 'diff'; execute(params: { - runtime: ReturnType['runtime']; + runtime: Awaited>['runtime']; sessionName: string; req: DaemonRequest; snapshotScope: string | undefined; @@ -66,7 +66,7 @@ export async function dispatchSnapshotRuntimeCommand( const { req, logPath, sessionStore } = params; const sessionName = ref?.address ?? params.sessionName; const capturedQuality: CapturedSnapshotQuality = {}; - const { runtime, sessions } = createSnapshotRuntime({ + const { runtime, sessions } = await createSnapshotRuntime({ createCommandSurface: params.createCommandSurface, req, sessionName, @@ -123,7 +123,7 @@ export async function dispatchSnapshotRuntimeCommand( ); } -function createSnapshotRuntime(params: { +async function createSnapshotRuntime(params: { createCommandSurface: CreateDaemonCommandSurface; req: DaemonRequest; sessionName: string; @@ -178,7 +178,7 @@ function createSnapshotRuntime(params: { return ref ?? sessionStore.publish(sessionName, nextSession); }, }); - const runtime = params.createCommandSurface({ + const runtime = await params.createCommandSurface({ backend: createDaemonSnapshotBackend({ req, logPath, diff --git a/src/daemon/wait-runtime.ts b/src/daemon/wait-runtime.ts index 73fb1176f2..70c18665d4 100644 --- a/src/daemon/wait-runtime.ts +++ b/src/daemon/wait-runtime.ts @@ -156,7 +156,7 @@ async function executeWaitRequest( recordedLandmark: TargetAnnotationV1 | undefined, ): Promise { const { req, sessionName, sessionStore } = params; - const runtime = createSelectorRuntimeForDevice({ + const runtime = await createSelectorRuntimeForDevice({ ...params, ref: params.ref, session,