@ai-agent-forge/plugin-desktop-windows-input 0.88.1 → 0.88.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAyGA;;;GAGG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,MAAM,CAAC,MAAM,CAAC;IAC9D,KAAK;IACL,UAAU;IACV,OAAO;IACP,SAAS;IACT,iBAAiB;CACiC,CAAC,CAAC;AAErD,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC,IAAyB,EAAW,EAAE,CACpF,iCAAoE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAoDtF,mFAAmF;AACnF,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAAC,OAA+B,EAAsB,EAAE,CAAC;IACxG,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;QAC5C,IAAI,SAAS,CAAC,SAAS,KAAK,OAAO,CAAC,SAAS;YAAE,OAAO,SAAS,CAAC,WAAW,CAAC;IAC7E,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB,CAAC","sourcesContent":["import type { PluginResultStatusV1 } from \"@agent-forge/plugin-sdk\";\n\n/**\n * Inlined from agent-forge `src/plugins/desktop/types.ts` (D-075 S3 extraction)\n * so this package keeps its dependency closure limited to the plugin SDK and\n * typebox. Semantics are unchanged. Differences from the host original:\n * - `CapabilityRefV1`/`PluginPlatformBindingV1` import from `@agent-forge/plugin-sdk`.\n * - The `agent-forge.desktop-control` test-only manifest contract types\n * (`DesktopPluginManifestV1`, `DesktopCapabilityDeclarationV1`) stay host-side;\n * they belong to the test-only mock plugin, not to this standalone package.\n * This file is a deliberate copy shared (not imported) with\n * `plugins/desktop-windows-observe/src/types.ts`; keep both copies in sync.\n */\n\n/** Stable capability ids owned by the desktop-control plugin family. */\nexport type DesktopCapabilityIdV1 =\n\t| \"desktop-session\"\n\t| \"desktop-uia\"\n\t| \"desktop-input\"\n\t| \"desktop-vision\"\n\t| \"desktop-replay\";\n\nexport type DesktopSessionIsolationV1 = \"mock\" | \"foreground\" | \"vm\";\nexport type DesktopSessionStatusV1 = \"ready\" | \"unavailable\" | \"stopped\";\n\nexport interface DesktopSessionRequestV1 {\n\treadonly version: 1;\n\treadonly isolation: DesktopSessionIsolationV1;\n\treadonly applicationId?: string;\n}\n\nexport interface DesktopSessionV1 {\n\treadonly version: 1;\n\treadonly sessionId: string;\n\treadonly isolation: DesktopSessionIsolationV1;\n\treadonly status: DesktopSessionStatusV1;\n\treadonly applicationId?: string;\n}\n\nexport interface DesktopErrorV1 {\n\treadonly version: 1;\n\treadonly code: string;\n\treadonly message: string;\n\treadonly causeCode?: string;\n}\n\nexport interface DesktopSessionResultV1 {\n\treadonly status: Extract<PluginResultStatusV1, \"succeeded\" | \"unsupported\" | \"failed\">;\n\treadonly session?: DesktopSessionV1;\n\treadonly error?: DesktopErrorV1;\n}\n\nexport interface DesktopWindowIdentityV1 {\n\treadonly version: 1;\n\treadonly windowId: string;\n\treadonly appId: string;\n\treadonly title: string;\n\treadonly bounds: DesktopBoundsV1;\n}\n\nexport interface DesktopControlIdentityV1 {\n\treadonly version: 1;\n\treadonly controlId: string;\n\treadonly windowId: string;\n\treadonly role: string;\n\treadonly name: string;\n\treadonly bounds: DesktopBoundsV1;\n\treadonly enabled: boolean;\n\treadonly value?: string;\n}\n\nexport interface DesktopBoundsV1 {\n\treadonly x: number;\n\treadonly y: number;\n\treadonly width: number;\n\treadonly height: number;\n}\n\nexport interface DesktopObservationV1 {\n\treadonly version: 1;\n\treadonly sessionId: string;\n\treadonly observationId: string;\n\treadonly source: \"mock\" | \"windows-uia\";\n\treadonly windows: readonly DesktopWindowIdentityV1[];\n\treadonly controls: readonly DesktopControlIdentityV1[];\n\treadonly focusedControlId?: string;\n}\n\nexport type DesktopTargetRefV1 =\n\t| { readonly kind: \"window\"; readonly windowId: string }\n\t| { readonly kind: \"control\"; readonly controlId: string; readonly windowId?: string };\n\nexport type DesktopActionKindV1 =\n\t| \"focus\"\n\t| \"click\"\n\t| \"type\"\n\t| \"key\"\n\t| \"scroll\"\n\t| \"submit\"\n\t| \"uac\"\n\t| \"password\"\n\t| \"admin\"\n\t| \"payment\"\n\t| \"external-submit\";\n\n/**\n * Actions that must pause at the host/user boundary before any input is sent.\n * This policy belongs to the desktop plugin, not Agent Core.\n */\nexport const DESKTOP_USER_CONFIRMATION_ACTIONS = Object.freeze([\n\t\"uac\",\n\t\"password\",\n\t\"admin\",\n\t\"payment\",\n\t\"external-submit\",\n] as const satisfies readonly DesktopActionKindV1[]);\n\nexport const requiresDesktopUserConfirmation = (kind: DesktopActionKindV1): boolean =>\n\t(DESKTOP_USER_CONFIRMATION_ACTIONS as readonly DesktopActionKindV1[]).includes(kind);\n\nexport interface DesktopActionV1 {\n\treadonly version: 1;\n\treadonly kind: DesktopActionKindV1;\n\treadonly target?: DesktopTargetRefV1;\n\treadonly text?: string;\n\treadonly key?: string;\n}\n\nexport type DesktopPostconditionV1 =\n\t| { readonly kind: \"window-exists\"; readonly windowId: string }\n\t| { readonly kind: \"control-exists\"; readonly controlId: string }\n\t| { readonly kind: \"focused\"; readonly controlId: string }\n\t| {\n\t\t\treadonly kind: \"property-equals\";\n\t\t\treadonly target: DesktopTargetRefV1;\n\t\t\treadonly property: \"enabled\" | \"value\" | \"name\";\n\t\t\treadonly expected: boolean | string;\n\t };\n\nexport interface DesktopOperationRequestV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly sessionId: string;\n\treadonly action: DesktopActionV1;\n\treadonly postconditions?: readonly DesktopPostconditionV1[];\n\treadonly reobserveAfterAction?: boolean;\n}\n\nexport interface DesktopVerificationV1 {\n\treadonly status: \"passed\" | \"failed\" | \"unknown\";\n\treadonly checks: readonly string[];\n}\n\nexport interface DesktopOperationResultV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly status: PluginResultStatusV1;\n\treadonly summary: string;\n\treadonly observation?: DesktopObservationV1;\n\treadonly verification?: DesktopVerificationV1;\n\treadonly error?: DesktopErrorV1;\n}\n\nexport interface DesktopReplayRequestV1 {\n\treadonly version: 1;\n\treadonly sessionId: string;\n\treadonly operations: readonly DesktopOperationRequestV1[];\n\treadonly stopOnFailure: boolean;\n}\n\n/** Returns the first operation id that would cross the replay session boundary. */\nexport const findDesktopReplaySessionMismatch = (request: DesktopReplayRequestV1): string | undefined => {\n\tfor (const operation of request.operations) {\n\t\tif (operation.sessionId !== request.sessionId) return operation.operationId;\n\t}\n\treturn undefined;\n};\n\nexport interface DesktopReplayResultV1 {\n\treadonly version: 1;\n\treadonly status: Extract<PluginResultStatusV1, \"succeeded\" | \"failed\" | \"cancelled\" | \"needs_user\">;\n\treadonly results: readonly DesktopOperationResultV1[];\n}\n\nexport interface DesktopAdapterV1 {\n\treadonly adapterId: string;\n\treadonly adapterVersion: string;\n\treadonly capabilities: readonly DesktopCapabilityIdV1[];\n\topenSession(request: DesktopSessionRequestV1): DesktopSessionResultV1;\n\tobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1;\n\treobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1;\n\texecute(request: DesktopOperationRequestV1): DesktopOperationResultV1;\n\tstop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1;\n\treplay(request: DesktopReplayRequestV1): DesktopReplayResultV1;\n}\n\n/** Result returned by a host UI Automation capability probe. */\nexport interface DesktopWindowsProbeV1 {\n\treadonly available: boolean;\n\treadonly platform: string;\n\treadonly driver: \"windows-uia\";\n\t/** Capabilities that the host can expose without relying on a higher-risk facade. */\n\treadonly capabilities?: readonly DesktopCapabilityIdV1[];\n\treadonly reason?: string;\n}\n\n/**\n * Synchronous host boundary used by the Windows adapter. Implementations may\n * use UI Automation, an OS broker, or a test driver. The adapter never\n * invents observations when this boundary reports unavailable.\n */\nexport interface DesktopWindowsDriverV1 {\n\treadonly driverId: string;\n\treadonly driverVersion: string;\n\tprobe(): DesktopWindowsProbeV1;\n\topenSession(request: DesktopSessionRequestV1): DesktopSessionResultV1;\n\tobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1;\n\texecute(request: DesktopOperationRequestV1): DesktopOperationResultV1;\n\tstop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1;\n}\n\n/**\n * Production-safe, read-only desktop boundary. It deliberately omits\n * execute/replay so a host cannot accidentally expose input through the\n * observe plugin. The full DesktopAdapter remains a separate test/high-risk\n * contract.\n */\nexport interface DesktopReadonlyAdapterV1 {\n\treadonly adapterId: string;\n\treadonly adapterVersion: string;\n\tprobe(): DesktopWindowsProbeV1;\n\topenSession(request: DesktopSessionRequestV1): DesktopSessionResultV1;\n\tobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1;\n\treobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1;\n\tstop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1;\n}\n\n/**\n * Production host boundary for intentionally bounded desktop input. This is\n * separate from DesktopReadonlyAdapterV1 so observing a desktop never\n * accidentally grants input, and replay is optional: adapters that expose it\n * keep the session-mismatch gate and the needs-user confirmation per action.\n */\nexport interface DesktopFullControlAdapterV1 extends DesktopReadonlyAdapterV1 {\n\texecute(request: DesktopOperationRequestV1): DesktopOperationResultV1;\n\treplay?(request: DesktopReplayRequestV1): DesktopReplayResultV1;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA2GA;;;GAGG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,MAAM,CAAC,MAAM,CAAC;IAC9D,KAAK;IACL,UAAU;IACV,OAAO;IACP,SAAS;IACT,iBAAiB;CACiC,CAAC,CAAC;AAErD,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC,IAAyB,EAAW,EAAE,CACpF,iCAAoE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAoDtF,mFAAmF;AACnF,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAAC,OAA+B,EAAsB,EAAE,CAAC;IACxG,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;QAC5C,IAAI,SAAS,CAAC,SAAS,KAAK,OAAO,CAAC,SAAS;YAAE,OAAO,SAAS,CAAC,WAAW,CAAC;IAC7E,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB,CAAC","sourcesContent":["import type { PluginResultStatusV1 } from \"@agent-forge/plugin-sdk\";\n\n/**\n * Inlined from agent-forge `src/plugins/desktop/types.ts` (D-075 S3 extraction)\n * so this package keeps its dependency closure limited to the plugin SDK and\n * typebox. Semantics are unchanged. Differences from the host original:\n * - `CapabilityRefV1`/`PluginPlatformBindingV1` import from `@agent-forge/plugin-sdk`.\n * - The `agent-forge.desktop-control` test-only manifest contract types\n * (`DesktopPluginManifestV1`, `DesktopCapabilityDeclarationV1`) stay host-side;\n * they belong to the test-only mock plugin, not to this standalone package.\n * This file is a deliberate copy shared (not imported) with\n * `plugins/desktop-windows-observe/src/types.ts`; keep both copies in sync.\n */\n\n/** Stable capability ids owned by the desktop-control plugin family. */\nexport type DesktopCapabilityIdV1 =\n\t| \"desktop-session\"\n\t| \"desktop-uia\"\n\t| \"desktop-input\"\n\t| \"desktop-vision\"\n\t| \"desktop-replay\";\n\nexport type DesktopSessionIsolationV1 = \"mock\" | \"foreground\" | \"vm\";\nexport type DesktopSessionStatusV1 = \"ready\" | \"unavailable\" | \"stopped\";\n\nexport interface DesktopSessionRequestV1 {\n\treadonly version: 1;\n\treadonly isolation: DesktopSessionIsolationV1;\n\treadonly applicationId?: string;\n}\n\nexport interface DesktopSessionV1 {\n\treadonly version: 1;\n\treadonly sessionId: string;\n\treadonly isolation: DesktopSessionIsolationV1;\n\treadonly status: DesktopSessionStatusV1;\n\treadonly applicationId?: string;\n}\n\nexport interface DesktopErrorV1 {\n\treadonly version: 1;\n\treadonly code: string;\n\treadonly message: string;\n\treadonly causeCode?: string;\n}\n\nexport interface DesktopSessionResultV1 {\n\treadonly status: Extract<PluginResultStatusV1, \"succeeded\" | \"unsupported\" | \"failed\">;\n\treadonly session?: DesktopSessionV1;\n\treadonly error?: DesktopErrorV1;\n}\n\nexport interface DesktopWindowIdentityV1 {\n\treadonly version: 1;\n\treadonly windowId: string;\n\treadonly appId: string;\n\treadonly title: string;\n\treadonly bounds: DesktopBoundsV1;\n}\n\nexport interface DesktopControlIdentityV1 {\n\treadonly version: 1;\n\treadonly controlId: string;\n\treadonly windowId: string;\n\treadonly role: string;\n\treadonly name: string;\n\treadonly bounds: DesktopBoundsV1;\n\treadonly enabled: boolean;\n\treadonly value?: string;\n}\n\nexport interface DesktopBoundsV1 {\n\treadonly x: number;\n\treadonly y: number;\n\treadonly width: number;\n\treadonly height: number;\n}\n\nexport interface DesktopObservationV1 {\n\treadonly version: 1;\n\treadonly sessionId: string;\n\treadonly observationId: string;\n\treadonly source: \"mock\" | \"windows-uia\";\n\treadonly windows: readonly DesktopWindowIdentityV1[];\n\treadonly controls: readonly DesktopControlIdentityV1[];\n\treadonly focusedControlId?: string;\n}\n\nexport type DesktopTargetRefV1 =\n\t| { readonly kind: \"window\"; readonly windowId: string }\n\t| { readonly kind: \"control\"; readonly controlId: string; readonly windowId?: string }\n\t/** Raw primary-screen pixel coordinates; only valid for click actions. */\n\t| { readonly kind: \"coordinates\"; readonly x: number; readonly y: number };\n\nexport type DesktopActionKindV1 =\n\t| \"focus\"\n\t| \"click\"\n\t| \"type\"\n\t| \"key\"\n\t| \"scroll\"\n\t| \"submit\"\n\t| \"uac\"\n\t| \"password\"\n\t| \"admin\"\n\t| \"payment\"\n\t| \"external-submit\";\n\n/**\n * Actions that must pause at the host/user boundary before any input is sent.\n * This policy belongs to the desktop plugin, not Agent Core.\n */\nexport const DESKTOP_USER_CONFIRMATION_ACTIONS = Object.freeze([\n\t\"uac\",\n\t\"password\",\n\t\"admin\",\n\t\"payment\",\n\t\"external-submit\",\n] as const satisfies readonly DesktopActionKindV1[]);\n\nexport const requiresDesktopUserConfirmation = (kind: DesktopActionKindV1): boolean =>\n\t(DESKTOP_USER_CONFIRMATION_ACTIONS as readonly DesktopActionKindV1[]).includes(kind);\n\nexport interface DesktopActionV1 {\n\treadonly version: 1;\n\treadonly kind: DesktopActionKindV1;\n\treadonly target?: DesktopTargetRefV1;\n\treadonly text?: string;\n\treadonly key?: string;\n}\n\nexport type DesktopPostconditionV1 =\n\t| { readonly kind: \"window-exists\"; readonly windowId: string }\n\t| { readonly kind: \"control-exists\"; readonly controlId: string }\n\t| { readonly kind: \"focused\"; readonly controlId: string }\n\t| {\n\t\t\treadonly kind: \"property-equals\";\n\t\t\treadonly target: DesktopTargetRefV1;\n\t\t\treadonly property: \"enabled\" | \"value\" | \"name\";\n\t\t\treadonly expected: boolean | string;\n\t };\n\nexport interface DesktopOperationRequestV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly sessionId: string;\n\treadonly action: DesktopActionV1;\n\treadonly postconditions?: readonly DesktopPostconditionV1[];\n\treadonly reobserveAfterAction?: boolean;\n}\n\nexport interface DesktopVerificationV1 {\n\treadonly status: \"passed\" | \"failed\" | \"unknown\";\n\treadonly checks: readonly string[];\n}\n\nexport interface DesktopOperationResultV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly status: PluginResultStatusV1;\n\treadonly summary: string;\n\treadonly observation?: DesktopObservationV1;\n\treadonly verification?: DesktopVerificationV1;\n\treadonly error?: DesktopErrorV1;\n}\n\nexport interface DesktopReplayRequestV1 {\n\treadonly version: 1;\n\treadonly sessionId: string;\n\treadonly operations: readonly DesktopOperationRequestV1[];\n\treadonly stopOnFailure: boolean;\n}\n\n/** Returns the first operation id that would cross the replay session boundary. */\nexport const findDesktopReplaySessionMismatch = (request: DesktopReplayRequestV1): string | undefined => {\n\tfor (const operation of request.operations) {\n\t\tif (operation.sessionId !== request.sessionId) return operation.operationId;\n\t}\n\treturn undefined;\n};\n\nexport interface DesktopReplayResultV1 {\n\treadonly version: 1;\n\treadonly status: Extract<PluginResultStatusV1, \"succeeded\" | \"failed\" | \"cancelled\" | \"needs_user\">;\n\treadonly results: readonly DesktopOperationResultV1[];\n}\n\n/** Structured result of one primary-display screenshot capture. */\nexport interface DesktopScreenshotResultV1 {\n\treadonly version: 1;\n\treadonly source: \"windows-gdi\";\n\treadonly status: \"succeeded\" | \"unsupported\" | \"failed\";\n\treadonly mimeType?: \"image/png\";\n\t/** Base64-encoded PNG payload; present only on success. */\n\treadonly data?: string;\n\t/** Delivered (post-scaling) pixel dimensions. */\n\treadonly width?: number;\n\treadonly height?: number;\n\t/** Captured display pixel dimensions before scaling. */\n\treadonly originalWidth?: number;\n\treadonly originalHeight?: number;\n\t/** originalWidth / width. */\n\treadonly scale?: number;\n\treadonly capturedAt?: string;\n\treadonly error?: DesktopErrorV1;\n}\n\n/**\n * Read-only GDI screenshot driver boundary. It stays separate from\n * DesktopWindowsDriverV1 so UIA-only driver implementations (test drivers,\n * remote brokers) are not forced to implement screen capture.\n */\nexport interface DesktopScreenshotDriverV1 {\n\treadonly driverId: string;\n\treadonly driverVersion: string;\n\tscreenshot(): DesktopScreenshotResultV1;\n}\n\nexport interface DesktopAdapterV1 {\n\treadonly adapterId: string;\n\treadonly adapterVersion: string;\n\treadonly capabilities: readonly DesktopCapabilityIdV1[];\n\topenSession(request: DesktopSessionRequestV1): DesktopSessionResultV1;\n\tobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1;\n\treobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1;\n\texecute(request: DesktopOperationRequestV1): DesktopOperationResultV1;\n\tstop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1;\n\treplay(request: DesktopReplayRequestV1): DesktopReplayResultV1;\n\t/** Read-only primary-display capture; adapters delegate to a screenshot-capable driver. */\n\tscreenshot(): DesktopScreenshotResultV1;\n}\n\n/** Result returned by a host UI Automation capability probe. */\nexport interface DesktopWindowsProbeV1 {\n\treadonly available: boolean;\n\treadonly platform: string;\n\treadonly driver: \"windows-uia\";\n\t/** Capabilities that the host can expose without relying on a higher-risk facade. */\n\treadonly capabilities?: readonly DesktopCapabilityIdV1[];\n\treadonly reason?: string;\n}\n\n/**\n * Synchronous host boundary used by the Windows adapter. Implementations may\n * use UI Automation, an OS broker, or a test driver. The adapter never\n * invents observations when this boundary reports unavailable.\n */\nexport interface DesktopWindowsDriverV1 {\n\treadonly driverId: string;\n\treadonly driverVersion: string;\n\tprobe(): DesktopWindowsProbeV1;\n\topenSession(request: DesktopSessionRequestV1): DesktopSessionResultV1;\n\tobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1;\n\texecute(request: DesktopOperationRequestV1): DesktopOperationResultV1;\n\tstop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1;\n}\n\n/**\n * Production-safe, read-only desktop boundary. It deliberately omits\n * execute/replay so a host cannot accidentally expose input through the\n * observe plugin. The full DesktopAdapter remains a separate test/high-risk\n * contract.\n */\nexport interface DesktopReadonlyAdapterV1 {\n\treadonly adapterId: string;\n\treadonly adapterVersion: string;\n\tprobe(): DesktopWindowsProbeV1;\n\topenSession(request: DesktopSessionRequestV1): DesktopSessionResultV1;\n\tobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1;\n\treobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1;\n\tstop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1;\n\t/** Read-only primary-display capture; adapters delegate to a screenshot-capable driver. */\n\tscreenshot(): DesktopScreenshotResultV1;\n}\n\n/**\n * Production host boundary for intentionally bounded desktop input. This is\n * separate from DesktopReadonlyAdapterV1 so observing a desktop never\n * accidentally grants input, and replay is optional: adapters that expose it\n * keep the session-mismatch gate and the needs-user confirmation per action.\n */\nexport interface DesktopFullControlAdapterV1 extends DesktopReadonlyAdapterV1 {\n\texecute(request: DesktopOperationRequestV1): DesktopOperationResultV1;\n\treplay?(request: DesktopReplayRequestV1): DesktopReplayResultV1;\n}\n"]}
@@ -5,7 +5,7 @@
5
5
  * facade intentionally exposes no replay method. This file pairs with the
6
6
  * inlined `windows-uia-adapter.ts` copy in this package.
7
7
  */
8
- import type { DesktopFullControlAdapterV1, DesktopObservationV1, DesktopOperationRequestV1, DesktopOperationResultV1, DesktopSessionRequestV1, DesktopSessionResultV1, DesktopSessionV1, DesktopWindowsDriverV1, DesktopWindowsProbeV1 } from "./types.ts";
8
+ import type { DesktopFullControlAdapterV1, DesktopObservationV1, DesktopOperationRequestV1, DesktopOperationResultV1, DesktopScreenshotResultV1, DesktopSessionRequestV1, DesktopSessionResultV1, DesktopSessionV1, DesktopWindowsDriverV1, DesktopWindowsProbeV1 } from "./types.ts";
9
9
  /**
10
10
  * The production input facade intentionally exposes no replay method. The
11
11
  * host driver remains responsible for using UIA target identities rather than
@@ -22,5 +22,7 @@ export declare class DesktopWindowsInputAdapter implements DesktopFullControlAda
22
22
  reobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1;
23
23
  execute(request: DesktopOperationRequestV1): DesktopOperationResultV1;
24
24
  stop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1;
25
+ /** Delegates to the driver when it implements DesktopScreenshotDriverV1; structured unsupported otherwise. */
26
+ screenshot(): DesktopScreenshotResultV1;
25
27
  }
26
28
  //# sourceMappingURL=windows-input-adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"windows-input-adapter.d.ts","sourceRoot":"","sources":["../src/windows-input-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACX,2BAA2B,EAC3B,oBAAoB,EACpB,yBAAyB,EACzB,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,EACtB,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACrB,MAAM,YAAY,CAAC;AAIpB;;;;GAIG;AACH,qBAAa,0BAA2B,YAAW,2BAA2B;IAC7E,QAAQ,CAAC,SAAS,yBAAyB;IAC3C,QAAQ,CAAC,cAAc,WAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAEhD,YAAY,MAAM,GAAE,sBAAwD,EAE3E;IAED,KAAK,IAAI,qBAAqB,CAE7B;IAED,WAAW,CAAC,OAAO,EAAE,uBAAuB,GAAG,sBAAsB,CAEpE;IAED,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,wBAAwB,CAE1E;IAED,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,wBAAwB,CAE5E;IAED,OAAO,CAAC,OAAO,EAAE,yBAAyB,GAAG,wBAAwB,CAgBpE;IAED,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG,wBAAwB,CAEnE;CACD","sourcesContent":["/**\n * Inlined from agent-forge `src/plugins/desktop/windows-input-adapter.ts`\n * (D-075 S3 extraction) so this package keeps its dependency closure limited\n * to the plugin SDK and typebox. Semantics are unchanged: the production input\n * facade intentionally exposes no replay method. This file pairs with the\n * inlined `windows-uia-adapter.ts` copy in this package.\n */\n\nimport type {\n\tDesktopFullControlAdapterV1,\n\tDesktopObservationV1,\n\tDesktopOperationRequestV1,\n\tDesktopOperationResultV1,\n\tDesktopSessionRequestV1,\n\tDesktopSessionResultV1,\n\tDesktopSessionV1,\n\tDesktopWindowsDriverV1,\n\tDesktopWindowsProbeV1,\n} from \"./types.ts\";\nimport { requiresDesktopUserConfirmation } from \"./types.ts\";\nimport { WindowsUiAutomationDriver } from \"./windows-uia-adapter.ts\";\n\n/**\n * The production input facade intentionally exposes no replay method. The\n * host driver remains responsible for using UIA target identities rather than\n * screen coordinates or global keyboard injection.\n */\nexport class DesktopWindowsInputAdapter implements DesktopFullControlAdapterV1 {\n\treadonly adapterId = \"desktop-windows-uia\";\n\treadonly adapterVersion = \"1.0.0\";\n\tprivate readonly driver: DesktopWindowsDriverV1;\n\n\tconstructor(driver: DesktopWindowsDriverV1 = new WindowsUiAutomationDriver()) {\n\t\tthis.driver = driver;\n\t}\n\n\tprobe(): DesktopWindowsProbeV1 {\n\t\treturn this.driver.probe();\n\t}\n\n\topenSession(request: DesktopSessionRequestV1): DesktopSessionResultV1 {\n\t\treturn this.driver.openSession(request);\n\t}\n\n\tobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.observe(sessionId);\n\t}\n\n\treobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.observe(sessionId);\n\t}\n\n\texecute(request: DesktopOperationRequestV1): DesktopOperationResultV1 {\n\t\tif (requiresDesktopUserConfirmation(request.action.kind)) {\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\toperationId: request.operationId,\n\t\t\t\tstatus: \"needs_user\",\n\t\t\t\tsummary: `User confirmation required for ${request.action.kind}`,\n\t\t\t\terror: {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\tcode: \"needs_user\",\n\t\t\t\t\tmessage: `The ${request.action.kind} action is paused for the user`,\n\t\t\t\t\tcauseCode: request.action.kind,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\treturn this.driver.execute(request);\n\t}\n\n\tstop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.stop(sessionId);\n\t}\n}\n"]}
1
+ {"version":3,"file":"windows-input-adapter.d.ts","sourceRoot":"","sources":["../src/windows-input-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACX,2BAA2B,EAC3B,oBAAoB,EACpB,yBAAyB,EACzB,wBAAwB,EAExB,yBAAyB,EACzB,uBAAuB,EACvB,sBAAsB,EACtB,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACrB,MAAM,YAAY,CAAC;AAIpB;;;;GAIG;AACH,qBAAa,0BAA2B,YAAW,2BAA2B;IAC7E,QAAQ,CAAC,SAAS,yBAAyB;IAC3C,QAAQ,CAAC,cAAc,WAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAEhD,YAAY,MAAM,GAAE,sBAAwD,EAE3E;IAED,KAAK,IAAI,qBAAqB,CAE7B;IAED,WAAW,CAAC,OAAO,EAAE,uBAAuB,GAAG,sBAAsB,CAEpE;IAED,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,wBAAwB,CAE1E;IAED,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,wBAAwB,CAE5E;IAED,OAAO,CAAC,OAAO,EAAE,yBAAyB,GAAG,wBAAwB,CAgBpE;IAED,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG,wBAAwB,CAEnE;IAED,8GAA8G;IAC9G,UAAU,IAAI,yBAAyB,CAetC;CACD","sourcesContent":["/**\n * Inlined from agent-forge `src/plugins/desktop/windows-input-adapter.ts`\n * (D-075 S3 extraction) so this package keeps its dependency closure limited\n * to the plugin SDK and typebox. Semantics are unchanged: the production input\n * facade intentionally exposes no replay method. This file pairs with the\n * inlined `windows-uia-adapter.ts` copy in this package.\n */\n\nimport type {\n\tDesktopFullControlAdapterV1,\n\tDesktopObservationV1,\n\tDesktopOperationRequestV1,\n\tDesktopOperationResultV1,\n\tDesktopScreenshotDriverV1,\n\tDesktopScreenshotResultV1,\n\tDesktopSessionRequestV1,\n\tDesktopSessionResultV1,\n\tDesktopSessionV1,\n\tDesktopWindowsDriverV1,\n\tDesktopWindowsProbeV1,\n} from \"./types.ts\";\nimport { requiresDesktopUserConfirmation } from \"./types.ts\";\nimport { WindowsUiAutomationDriver } from \"./windows-uia-adapter.ts\";\n\n/**\n * The production input facade intentionally exposes no replay method. The\n * host driver remains responsible for using UIA target identities rather than\n * screen coordinates or global keyboard injection.\n */\nexport class DesktopWindowsInputAdapter implements DesktopFullControlAdapterV1 {\n\treadonly adapterId = \"desktop-windows-uia\";\n\treadonly adapterVersion = \"1.0.0\";\n\tprivate readonly driver: DesktopWindowsDriverV1;\n\n\tconstructor(driver: DesktopWindowsDriverV1 = new WindowsUiAutomationDriver()) {\n\t\tthis.driver = driver;\n\t}\n\n\tprobe(): DesktopWindowsProbeV1 {\n\t\treturn this.driver.probe();\n\t}\n\n\topenSession(request: DesktopSessionRequestV1): DesktopSessionResultV1 {\n\t\treturn this.driver.openSession(request);\n\t}\n\n\tobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.observe(sessionId);\n\t}\n\n\treobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.observe(sessionId);\n\t}\n\n\texecute(request: DesktopOperationRequestV1): DesktopOperationResultV1 {\n\t\tif (requiresDesktopUserConfirmation(request.action.kind)) {\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\toperationId: request.operationId,\n\t\t\t\tstatus: \"needs_user\",\n\t\t\t\tsummary: `User confirmation required for ${request.action.kind}`,\n\t\t\t\terror: {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\tcode: \"needs_user\",\n\t\t\t\t\tmessage: `The ${request.action.kind} action is paused for the user`,\n\t\t\t\t\tcauseCode: request.action.kind,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\treturn this.driver.execute(request);\n\t}\n\n\tstop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.stop(sessionId);\n\t}\n\n\t/** Delegates to the driver when it implements DesktopScreenshotDriverV1; structured unsupported otherwise. */\n\tscreenshot(): DesktopScreenshotResultV1 {\n\t\tconst driver = this.driver as DesktopWindowsDriverV1 & Partial<DesktopScreenshotDriverV1>;\n\t\tif (typeof driver.screenshot !== \"function\")\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\tsource: \"windows-gdi\",\n\t\t\t\tstatus: \"unsupported\",\n\t\t\t\terror: {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\tcode: \"driver_unavailable\",\n\t\t\t\t\tmessage: \"The selected desktop driver does not support screenshots\",\n\t\t\t\t\tcauseCode: \"screenshot-driver-unavailable\",\n\t\t\t\t},\n\t\t\t};\n\t\treturn driver.screenshot();\n\t}\n}\n"]}
@@ -51,5 +51,22 @@ export class DesktopWindowsInputAdapter {
51
51
  stop(sessionId) {
52
52
  return this.driver.stop(sessionId);
53
53
  }
54
+ /** Delegates to the driver when it implements DesktopScreenshotDriverV1; structured unsupported otherwise. */
55
+ screenshot() {
56
+ const driver = this.driver;
57
+ if (typeof driver.screenshot !== "function")
58
+ return {
59
+ version: 1,
60
+ source: "windows-gdi",
61
+ status: "unsupported",
62
+ error: {
63
+ version: 1,
64
+ code: "driver_unavailable",
65
+ message: "The selected desktop driver does not support screenshots",
66
+ causeCode: "screenshot-driver-unavailable",
67
+ },
68
+ };
69
+ return driver.screenshot();
70
+ }
54
71
  }
55
72
  //# sourceMappingURL=windows-input-adapter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"windows-input-adapter.js","sourceRoot":"","sources":["../src/windows-input-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH,OAAO,EAAE,+BAA+B,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAErE;;;;GAIG;AACH,MAAM,OAAO,0BAA0B;IAC7B,SAAS,GAAG,qBAAqB,CAAC;IAClC,cAAc,GAAG,OAAO,CAAC;IACjB,MAAM,CAAyB;IAEhD,YAAY,MAAM,GAA2B,IAAI,yBAAyB,EAAE,EAAE;QAC7E,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IAAA,CACrB;IAED,KAAK,GAA0B;QAC9B,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IAAA,CAC3B;IAED,WAAW,CAAC,OAAgC,EAA0B;QACrE,OAAO,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAAA,CACxC;IAED,OAAO,CAAC,SAAiB,EAAmD;QAC3E,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAAA,CACtC;IAED,SAAS,CAAC,SAAiB,EAAmD;QAC7E,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAAA,CACtC;IAED,OAAO,CAAC,OAAkC,EAA4B;QACrE,IAAI,+BAA+B,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1D,OAAO;gBACN,OAAO,EAAE,CAAC;gBACV,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,MAAM,EAAE,YAAY;gBACpB,OAAO,EAAE,kCAAkC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE;gBAChE,KAAK,EAAE;oBACN,OAAO,EAAE,CAAC;oBACV,IAAI,EAAE,YAAY;oBAClB,OAAO,EAAE,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,gCAAgC;oBACnE,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI;iBAC9B;aACD,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAAA,CACpC;IAED,IAAI,CAAC,SAAiB,EAA+C;QACpE,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAAA,CACnC;CACD","sourcesContent":["/**\n * Inlined from agent-forge `src/plugins/desktop/windows-input-adapter.ts`\n * (D-075 S3 extraction) so this package keeps its dependency closure limited\n * to the plugin SDK and typebox. Semantics are unchanged: the production input\n * facade intentionally exposes no replay method. This file pairs with the\n * inlined `windows-uia-adapter.ts` copy in this package.\n */\n\nimport type {\n\tDesktopFullControlAdapterV1,\n\tDesktopObservationV1,\n\tDesktopOperationRequestV1,\n\tDesktopOperationResultV1,\n\tDesktopSessionRequestV1,\n\tDesktopSessionResultV1,\n\tDesktopSessionV1,\n\tDesktopWindowsDriverV1,\n\tDesktopWindowsProbeV1,\n} from \"./types.ts\";\nimport { requiresDesktopUserConfirmation } from \"./types.ts\";\nimport { WindowsUiAutomationDriver } from \"./windows-uia-adapter.ts\";\n\n/**\n * The production input facade intentionally exposes no replay method. The\n * host driver remains responsible for using UIA target identities rather than\n * screen coordinates or global keyboard injection.\n */\nexport class DesktopWindowsInputAdapter implements DesktopFullControlAdapterV1 {\n\treadonly adapterId = \"desktop-windows-uia\";\n\treadonly adapterVersion = \"1.0.0\";\n\tprivate readonly driver: DesktopWindowsDriverV1;\n\n\tconstructor(driver: DesktopWindowsDriverV1 = new WindowsUiAutomationDriver()) {\n\t\tthis.driver = driver;\n\t}\n\n\tprobe(): DesktopWindowsProbeV1 {\n\t\treturn this.driver.probe();\n\t}\n\n\topenSession(request: DesktopSessionRequestV1): DesktopSessionResultV1 {\n\t\treturn this.driver.openSession(request);\n\t}\n\n\tobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.observe(sessionId);\n\t}\n\n\treobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.observe(sessionId);\n\t}\n\n\texecute(request: DesktopOperationRequestV1): DesktopOperationResultV1 {\n\t\tif (requiresDesktopUserConfirmation(request.action.kind)) {\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\toperationId: request.operationId,\n\t\t\t\tstatus: \"needs_user\",\n\t\t\t\tsummary: `User confirmation required for ${request.action.kind}`,\n\t\t\t\terror: {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\tcode: \"needs_user\",\n\t\t\t\t\tmessage: `The ${request.action.kind} action is paused for the user`,\n\t\t\t\t\tcauseCode: request.action.kind,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\treturn this.driver.execute(request);\n\t}\n\n\tstop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.stop(sessionId);\n\t}\n}\n"]}
1
+ {"version":3,"file":"windows-input-adapter.js","sourceRoot":"","sources":["../src/windows-input-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH,OAAO,EAAE,+BAA+B,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAErE;;;;GAIG;AACH,MAAM,OAAO,0BAA0B;IAC7B,SAAS,GAAG,qBAAqB,CAAC;IAClC,cAAc,GAAG,OAAO,CAAC;IACjB,MAAM,CAAyB;IAEhD,YAAY,MAAM,GAA2B,IAAI,yBAAyB,EAAE,EAAE;QAC7E,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IAAA,CACrB;IAED,KAAK,GAA0B;QAC9B,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IAAA,CAC3B;IAED,WAAW,CAAC,OAAgC,EAA0B;QACrE,OAAO,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAAA,CACxC;IAED,OAAO,CAAC,SAAiB,EAAmD;QAC3E,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAAA,CACtC;IAED,SAAS,CAAC,SAAiB,EAAmD;QAC7E,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAAA,CACtC;IAED,OAAO,CAAC,OAAkC,EAA4B;QACrE,IAAI,+BAA+B,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1D,OAAO;gBACN,OAAO,EAAE,CAAC;gBACV,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,MAAM,EAAE,YAAY;gBACpB,OAAO,EAAE,kCAAkC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE;gBAChE,KAAK,EAAE;oBACN,OAAO,EAAE,CAAC;oBACV,IAAI,EAAE,YAAY;oBAClB,OAAO,EAAE,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,gCAAgC;oBACnE,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI;iBAC9B;aACD,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAAA,CACpC;IAED,IAAI,CAAC,SAAiB,EAA+C;QACpE,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAAA,CACnC;IAED,8GAA8G;IAC9G,UAAU,GAA8B;QACvC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAqE,CAAC;QAC1F,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,UAAU;YAC1C,OAAO;gBACN,OAAO,EAAE,CAAC;gBACV,MAAM,EAAE,aAAa;gBACrB,MAAM,EAAE,aAAa;gBACrB,KAAK,EAAE;oBACN,OAAO,EAAE,CAAC;oBACV,IAAI,EAAE,oBAAoB;oBAC1B,OAAO,EAAE,0DAA0D;oBACnE,SAAS,EAAE,+BAA+B;iBAC1C;aACD,CAAC;QACH,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC;IAAA,CAC3B;CACD","sourcesContent":["/**\n * Inlined from agent-forge `src/plugins/desktop/windows-input-adapter.ts`\n * (D-075 S3 extraction) so this package keeps its dependency closure limited\n * to the plugin SDK and typebox. Semantics are unchanged: the production input\n * facade intentionally exposes no replay method. This file pairs with the\n * inlined `windows-uia-adapter.ts` copy in this package.\n */\n\nimport type {\n\tDesktopFullControlAdapterV1,\n\tDesktopObservationV1,\n\tDesktopOperationRequestV1,\n\tDesktopOperationResultV1,\n\tDesktopScreenshotDriverV1,\n\tDesktopScreenshotResultV1,\n\tDesktopSessionRequestV1,\n\tDesktopSessionResultV1,\n\tDesktopSessionV1,\n\tDesktopWindowsDriverV1,\n\tDesktopWindowsProbeV1,\n} from \"./types.ts\";\nimport { requiresDesktopUserConfirmation } from \"./types.ts\";\nimport { WindowsUiAutomationDriver } from \"./windows-uia-adapter.ts\";\n\n/**\n * The production input facade intentionally exposes no replay method. The\n * host driver remains responsible for using UIA target identities rather than\n * screen coordinates or global keyboard injection.\n */\nexport class DesktopWindowsInputAdapter implements DesktopFullControlAdapterV1 {\n\treadonly adapterId = \"desktop-windows-uia\";\n\treadonly adapterVersion = \"1.0.0\";\n\tprivate readonly driver: DesktopWindowsDriverV1;\n\n\tconstructor(driver: DesktopWindowsDriverV1 = new WindowsUiAutomationDriver()) {\n\t\tthis.driver = driver;\n\t}\n\n\tprobe(): DesktopWindowsProbeV1 {\n\t\treturn this.driver.probe();\n\t}\n\n\topenSession(request: DesktopSessionRequestV1): DesktopSessionResultV1 {\n\t\treturn this.driver.openSession(request);\n\t}\n\n\tobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.observe(sessionId);\n\t}\n\n\treobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.observe(sessionId);\n\t}\n\n\texecute(request: DesktopOperationRequestV1): DesktopOperationResultV1 {\n\t\tif (requiresDesktopUserConfirmation(request.action.kind)) {\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\toperationId: request.operationId,\n\t\t\t\tstatus: \"needs_user\",\n\t\t\t\tsummary: `User confirmation required for ${request.action.kind}`,\n\t\t\t\terror: {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\tcode: \"needs_user\",\n\t\t\t\t\tmessage: `The ${request.action.kind} action is paused for the user`,\n\t\t\t\t\tcauseCode: request.action.kind,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\treturn this.driver.execute(request);\n\t}\n\n\tstop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.stop(sessionId);\n\t}\n\n\t/** Delegates to the driver when it implements DesktopScreenshotDriverV1; structured unsupported otherwise. */\n\tscreenshot(): DesktopScreenshotResultV1 {\n\t\tconst driver = this.driver as DesktopWindowsDriverV1 & Partial<DesktopScreenshotDriverV1>;\n\t\tif (typeof driver.screenshot !== \"function\")\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\tsource: \"windows-gdi\",\n\t\t\t\tstatus: \"unsupported\",\n\t\t\t\terror: {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\tcode: \"driver_unavailable\",\n\t\t\t\t\tmessage: \"The selected desktop driver does not support screenshots\",\n\t\t\t\t\tcauseCode: \"screenshot-driver-unavailable\",\n\t\t\t\t},\n\t\t\t};\n\t\treturn driver.screenshot();\n\t}\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { DesktopAdapterV1, DesktopCapabilityIdV1, DesktopObservationV1, DesktopOperationRequestV1, DesktopOperationResultV1, DesktopReplayRequestV1, DesktopReplayResultV1, DesktopSessionRequestV1, DesktopSessionResultV1, DesktopSessionV1, DesktopWindowsDriverV1, DesktopWindowsProbeV1 } from "./types.ts";
1
+ import type { DesktopAdapterV1, DesktopCapabilityIdV1, DesktopObservationV1, DesktopOperationRequestV1, DesktopOperationResultV1, DesktopReplayRequestV1, DesktopReplayResultV1, DesktopScreenshotDriverV1, DesktopScreenshotResultV1, DesktopSessionRequestV1, DesktopSessionResultV1, DesktopSessionV1, DesktopWindowsDriverV1, DesktopWindowsProbeV1 } from "./types.ts";
2
2
  type UiAutomationSpawnResult = {
3
3
  readonly error?: Error;
4
4
  readonly status: number | null;
@@ -9,6 +9,8 @@ type UiAutomationSpawn = (command: string, args: readonly string[], options: {
9
9
  readonly timeout: number;
10
10
  readonly windowsHide: boolean;
11
11
  readonly env: NodeJS.ProcessEnv;
12
+ /** Raised only for the screenshot script so multi-MiB PNG payloads survive. */
13
+ readonly maxBuffer?: number;
12
14
  }) => UiAutomationSpawnResult;
13
15
  export interface WindowsUiAutomationDriverOptions {
14
16
  /** Override the PowerShell executable for a brokered host or test harness. */
@@ -19,7 +21,7 @@ export interface WindowsUiAutomationDriverOptions {
19
21
  /** Override the platform reported to the driver; intended for host adapters/tests. */
20
22
  readonly platform?: NodeJS.Platform;
21
23
  }
22
- export declare class WindowsUiAutomationDriver implements DesktopWindowsDriverV1 {
24
+ export declare class WindowsUiAutomationDriver implements DesktopWindowsDriverV1, DesktopScreenshotDriverV1 {
23
25
  readonly driverId = "windows-uia";
24
26
  readonly driverVersion = "1.0.0";
25
27
  private readonly command;
@@ -38,7 +40,10 @@ export declare class WindowsUiAutomationDriver implements DesktopWindowsDriverV1
38
40
  openSession(request: DesktopSessionRequestV1): DesktopSessionResultV1;
39
41
  observe(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1;
40
42
  execute(request: DesktopOperationRequestV1): DesktopOperationResultV1;
43
+ private completeAction;
41
44
  stop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1;
45
+ /** Capture the primary display through GDI; the PNG is scaled to a 1280px width cap. */
46
+ screenshot(): DesktopScreenshotResultV1;
42
47
  private run;
43
48
  private toObservation;
44
49
  private resolveTarget;
@@ -62,6 +67,8 @@ export declare class DesktopWindowsAdapter implements DesktopAdapterV1 {
62
67
  reobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1;
63
68
  execute(request: DesktopOperationRequestV1): DesktopOperationResultV1;
64
69
  stop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1;
70
+ /** Delegates to the driver when it implements DesktopScreenshotDriverV1; structured unsupported otherwise. */
71
+ screenshot(): DesktopScreenshotResultV1;
65
72
  replay(request: DesktopReplayRequestV1): DesktopReplayResultV1;
66
73
  }
67
74
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"windows-uia-adapter.d.ts","sourceRoot":"","sources":["../src/windows-uia-adapter.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACX,gBAAgB,EAChB,qBAAqB,EAGrB,oBAAoB,EACpB,yBAAyB,EACzB,wBAAwB,EAExB,sBAAsB,EACtB,qBAAqB,EACrB,uBAAuB,EACvB,sBAAsB,EACtB,gBAAgB,EAIhB,sBAAsB,EACtB,qBAAqB,EACrB,MAAM,YAAY,CAAC;AA+BpB,KAAK,uBAAuB,GAAG;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,iBAAiB,GAAG,CACxB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE;IACR,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;CAChC,KACG,uBAAuB,CAAC;AAE7B,MAAM,WAAW,gCAAgC;IAChD,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,iFAAiF;IACjF,QAAQ,CAAC,SAAS,CAAC,EAAE,iBAAiB,CAAC;IACvC,sFAAsF;IACtF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CACpC;AAoSD,qBAAa,yBAA0B,YAAW,sBAAsB;IACvE,QAAQ,CAAC,QAAQ,iBAAiB;IAClC,QAAQ,CAAC,aAAa,WAAW;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAChE,OAAO,CAAC,QAAQ,CAAC,OAAO,CASpB;IACJ,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAkC;IACpE,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAA6B;IAClE,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;IACtD,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,iBAAiB,CAAK;IAE9B,YAAY,OAAO,GAAE,gCAAqC,EAQzD;IAED,KAAK,IAAI,qBAAqB,CA8B7B;IAED,WAAW,CAAC,OAAO,EAAE,uBAAuB,GAAG,sBAAsB,CAoBpE;IAED,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,wBAAwB,CAU1E;IAED,OAAO,CAAC,OAAO,EAAE,yBAAyB,GAAG,wBAAwB,CAkGpE;IAED,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG,wBAAwB,CAOnE;IAED,OAAO,CAAC,GAAG;IAiBX,OAAO,CAAC,aAAa;IAiFrB,OAAO,CAAC,aAAa;IAmBrB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,qBAAqB;IAM7B,OAAO,CAAC,oBAAoB;IAgB5B,OAAO,CAAC,kBAAkB;IAsB1B,OAAO,CAAC,MAAM;IAGd,OAAO,CAAC,SAAS;CASjB;AAED,uFAAuF;AACvF,qBAAa,qBAAsB,YAAW,gBAAgB;IAC7D,QAAQ,CAAC,SAAS,yBAAyB;IAC3C,QAAQ,CAAC,cAAc,WAAW;IAClC,QAAQ,CAAC,YAAY,mCAAyB;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAChD,YAAY,MAAM,GAAE,sBAAwD,EAE3E;IACD,KAAK,IAAI,qBAAqB,CAE7B;IACD,WAAW,CAAC,OAAO,EAAE,uBAAuB,GAAG,sBAAsB,CAEpE;IACD,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,wBAAwB,CAE1E;IACD,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,wBAAwB,CAE5E;IACD,OAAO,CAAC,OAAO,EAAE,yBAAyB,GAAG,wBAAwB,CAWpE;IACD,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG,wBAAwB,CAEnE;IACD,MAAM,CAAC,OAAO,EAAE,sBAAsB,GAAG,qBAAqB,CAqC7D;CACD","sourcesContent":["/**\n * Inlined from agent-forge `src/plugins/desktop/windows-uia-adapter.ts`\n * (D-075 S3 extraction) so this package keeps its dependency closure limited\n * to the plugin SDK and typebox. Semantics are unchanged: the full driver\n * (query + action PowerShell scripts) is carried completely even though this\n * observe package only exposes the read-only capability surface. This file is\n * a deliberate copy shared (not imported) with\n * `plugins/desktop-windows-observe/src/windows-uia-adapter.ts`; keep both\n * copies in sync.\n */\nimport { spawnSync as nodeSpawnSync } from \"node:child_process\";\nimport { detachedFreeze } from \"@agent-forge/plugin-sdk\";\nimport type {\n\tDesktopAdapterV1,\n\tDesktopCapabilityIdV1,\n\tDesktopControlIdentityV1,\n\tDesktopErrorV1,\n\tDesktopObservationV1,\n\tDesktopOperationRequestV1,\n\tDesktopOperationResultV1,\n\tDesktopPostconditionV1,\n\tDesktopReplayRequestV1,\n\tDesktopReplayResultV1,\n\tDesktopSessionRequestV1,\n\tDesktopSessionResultV1,\n\tDesktopSessionV1,\n\tDesktopTargetRefV1,\n\tDesktopVerificationV1,\n\tDesktopWindowIdentityV1,\n\tDesktopWindowsDriverV1,\n\tDesktopWindowsProbeV1,\n} from \"./types.ts\";\nimport { findDesktopReplaySessionMismatch, requiresDesktopUserConfirmation } from \"./types.ts\";\n\ntype RawWindow = {\n\thandle?: number;\n\tprocessId?: number;\n\tname?: string;\n\tx?: number;\n\ty?: number;\n\twidth?: number;\n\theight?: number;\n};\ntype RawControl = {\n\twindowHandle?: number;\n\tnativeWindowHandle?: number;\n\tcontrolId?: string;\n\tname?: string;\n\trole?: string;\n\tenabled?: boolean;\n\tvalue?: string;\n\tx?: number;\n\ty?: number;\n\twidth?: number;\n\theight?: number;\n};\ntype RawSnapshot = {\n\twindows?: RawWindow[];\n\tcontrols?: RawControl[];\n\tfocusedControl?: { windowHandle?: number; controlId?: string };\n};\n\ntype UiAutomationSpawnResult = {\n\treadonly error?: Error;\n\treadonly status: number | null;\n\treadonly stdout: string;\n};\n\ntype UiAutomationSpawn = (\n\tcommand: string,\n\targs: readonly string[],\n\toptions: {\n\t\treadonly encoding: \"utf8\";\n\t\treadonly timeout: number;\n\t\treadonly windowsHide: boolean;\n\t\treadonly env: NodeJS.ProcessEnv;\n\t},\n) => UiAutomationSpawnResult;\n\nexport interface WindowsUiAutomationDriverOptions {\n\t/** Override the PowerShell executable for a brokered host or test harness. */\n\treadonly command?: string;\n\treadonly timeoutMs?: number;\n\t/** Injectable host boundary for deterministic tests and brokered deployments. */\n\treadonly spawnSync?: UiAutomationSpawn;\n\t/** Override the platform reported to the driver; intended for host adapters/tests. */\n\treadonly platform?: NodeJS.Platform;\n}\n\nconst READONLY_CAPABILITIES: readonly DesktopCapabilityIdV1[] = [\"desktop-session\", \"desktop-uia\"];\nconst SUPPORTED_ACTIONS: readonly DesktopOperationRequestV1[\"action\"][\"kind\"][] = [\n\t\"focus\",\n\t\"click\",\n\t\"type\",\n\t\"key\",\n\t\"scroll\",\n];\n\nconst error = (code: string, message: string, causeCode?: string): DesktopErrorV1 => ({\n\tversion: 1,\n\tcode,\n\tmessage,\n\t...(causeCode === undefined ? {} : { causeCode }),\n});\n\nconst unsupported = (\n\toperationId: string,\n\treason: string,\n\tcauseCode = \"platform-unavailable\",\n): DesktopOperationResultV1 => ({\n\tversion: 1,\n\toperationId,\n\tstatus: \"unsupported\",\n\tsummary: reason,\n\terror: error(causeCode === \"action-unsupported\" ? \"action_unsupported\" : \"platform_unavailable\", reason, causeCode),\n});\n\nconst rectangle = (raw: { x?: number; y?: number; width?: number; height?: number }) => ({\n\tx: Number.isFinite(raw.x) ? Number(raw.x) : 0,\n\ty: Number.isFinite(raw.y) ? Number(raw.y) : 0,\n\twidth: Number.isFinite(raw.width) ? Number(raw.width) : 0,\n\theight: Number.isFinite(raw.height) ? Number(raw.height) : 0,\n});\n\nconst QUERY_SCRIPT = `\ntry {\n Add-Type -AssemblyName UIAutomationClient -ErrorAction Stop\n Add-Type -AssemblyName UIAutomationTypes -ErrorAction Stop\n function SafeInt($value) {\n $number = [double]$value\n if ([double]::IsNaN($number) -or [double]::IsInfinity($number)) { return 0 }\n return [int][math]::Round($number)\n }\n function Get-ControlId($node, [int]$index) {\n $automationId = [string]$node.Current.AutomationId\n $name = [string]$node.Current.Name\n $role = [string]$node.Current.ControlType.ProgrammaticName\n if ($automationId.Length -gt 0) { return $automationId }\n if ($name.Length -gt 0) { return $role + ':' + $name }\n return $role + ':' + $index\n }\n $root = [System.Windows.Automation.AutomationElement]::RootElement\n $windowWalker = [System.Windows.Automation.TreeWalker]::ControlViewWalker\n $windows = [System.Collections.Generic.List[object]]::new()\n $controls = [System.Collections.Generic.List[object]]::new()\n $focusedMatches = [System.Collections.Generic.List[object]]::new()\n $focused = $null\n try { $focused = [System.Windows.Automation.AutomationElement]::FocusedElement } catch {}\n $maxWindows = 256\n $maxControlsPerWindow = 1000\n $maxControlsTotal = 10000\n $maxDepth = 32\n function Add-Descendants($node, [int64]$windowHandle, [int]$depth, [ref]$count) {\n if ($null -eq $node -or $depth -gt $maxDepth -or $count.Value -ge $maxControlsPerWindow -or $controls.Count -ge $maxControlsTotal) { return }\n $index = $count.Value\n $count.Value = $count.Value + 1\n $childRect = $node.Current.BoundingRectangle\n $controlId = Get-ControlId $node $index\n $value = $null\n try {\n $pattern = $null\n if ($node.TryGetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern, [ref]$pattern)) { $value = [string]$pattern.Current.Value }\n } catch {}\n $name = [string]$node.Current.Name\n $nativeWindowHandle = [int64]$node.Current.NativeWindowHandle\n if ($null -eq $value -and $name.Length -gt 0) { $value = $name }\n $controls.Add([pscustomobject]@{ windowHandle=$windowHandle; nativeWindowHandle=$nativeWindowHandle; controlId=$controlId; name=$name; role=[string]$node.Current.ControlType.ProgrammaticName; enabled=[bool]$node.Current.IsEnabled; value=$value; x=(SafeInt $childRect.X); y=(SafeInt $childRect.Y); width=(SafeInt $childRect.Width); height=(SafeInt $childRect.Height) })\n try {\n if ($null -ne $focused -and $node.Equals($focused)) { $focusedMatches.Add([pscustomobject]@{ windowHandle=$windowHandle; controlId=$controlId }) }\n } catch {}\n $child = $windowWalker.GetFirstChild($node)\n while ($null -ne $child -and $count.Value -lt $maxControlsPerWindow) {\n Add-Descendants $child $windowHandle ($depth + 1) $count\n $child = $windowWalker.GetNextSibling($child)\n }\n }\n $window = $windowWalker.GetFirstChild($root)\n $windowCount = 0\n while ($null -ne $window -and $windowCount -lt $maxWindows) {\n $handle = [int64]$window.Current.NativeWindowHandle\n if ($handle -ne 0) {\n $rect = $window.Current.BoundingRectangle\n $processId = [int]$window.Current.ProcessId\n $windows.Add([pscustomobject]@{ handle=$handle; processId=$processId; name=[string]$window.Current.Name; x=(SafeInt $rect.X); y=(SafeInt $rect.Y); width=(SafeInt $rect.Width); height=(SafeInt $rect.Height) })\n $windowCount++\n $child = $windowWalker.GetFirstChild($window)\n $count = 0\n while ($null -ne $child -and $count -lt $maxControlsPerWindow -and $controls.Count -lt $maxControlsTotal) {\n Add-Descendants $child $handle 1 ([ref]$count)\n $child = $windowWalker.GetNextSibling($child)\n }\n }\n $window = $windowWalker.GetNextSibling($window)\n }\n $focusedControl = if ($focusedMatches.Count -gt 0) { $focusedMatches[0] } else { $null }\n [pscustomobject]@{ windows=@($windows); controls=@($controls); focusedControl=$focusedControl; available=$true } | ConvertTo-Json -Compress -Depth 8\n} catch {\n [pscustomobject]@{ available=$false; error=[string]$_.Exception.Message } | ConvertTo-Json -Compress -Depth 5\n}`;\n\nconst ACTION_SCRIPT = `\ntry {\n Add-Type -AssemblyName UIAutomationClient -ErrorAction Stop\n Add-Type -AssemblyName UIAutomationTypes -ErrorAction Stop\n $payload = $env:AGENT_FORGE_UIA_INPUT | ConvertFrom-Json\n $handle = [int64]$payload.target.windowHandle\n $window = [System.Windows.Automation.AutomationElement]::FromHandle([IntPtr]$handle)\n if ($null -eq $window) { throw 'Window handle is no longer available' }\n $walker = [System.Windows.Automation.TreeWalker]::ControlViewWalker\n $target = $window\n $script:AgentForgeTarget = $null\n if ($null -ne $payload.target.controlId) {\n $controlHandle = [int64]$payload.target.controlWindowHandle\n if ($controlHandle -gt 0) {\n try {\n $candidate = [System.Windows.Automation.AutomationElement]::FromHandle([IntPtr]$controlHandle)\n if ($null -ne $candidate) {\n $candidateAutomationId = [string]$candidate.Current.AutomationId\n $candidateName = [string]$candidate.Current.Name\n $candidateRole = [string]$candidate.Current.ControlType.ProgrammaticName\n $candidateId = if ($candidateAutomationId.Length -gt 0) { $candidateAutomationId } elseif ($candidateName.Length -gt 0) { $candidateRole + ':' + $candidateName } else { $null }\n if ($candidateId -eq [string]$payload.target.controlId) { $script:AgentForgeTarget = $candidate }\n }\n } catch {}\n }\n $maxDepth = 32\n $maxVisited = 1000\n function Find-Descendant($node, [int]$depth, [ref]$visited) {\n if ($null -eq $node -or $depth -gt $maxDepth -or $visited.Value -ge $maxVisited -or $null -ne $script:AgentForgeTarget) { return }\n $visited.Value = $visited.Value + 1\n $automationId = [string]$node.Current.AutomationId\n $name = [string]$node.Current.Name\n $role = [string]$node.Current.ControlType.ProgrammaticName\n $id = if ($automationId.Length -gt 0) { $automationId } elseif ($name.Length -gt 0) { $role + ':' + $name } else { $role + ':' + ($visited.Value - 1) }\n if ($id -eq [string]$payload.target.controlId) { $script:AgentForgeTarget = $node; return }\n $child = $walker.GetFirstChild($node)\n while ($null -ne $child -and $visited.Value -lt $maxVisited -and $null -eq $script:AgentForgeTarget) {\n Find-Descendant $child ($depth + 1) $visited\n $child = $walker.GetNextSibling($child)\n }\n }\n $visited = 0\n if ($null -eq $script:AgentForgeTarget) {\n $node = $walker.GetFirstChild($window)\n while ($null -ne $node -and $visited -lt $maxVisited -and $null -eq $script:AgentForgeTarget) {\n Find-Descendant $node 1 ([ref]$visited)\n $node = $walker.GetNextSibling($node)\n }\n }\n if ($null -eq $script:AgentForgeTarget) { throw ('Control was not found: ' + [string]$payload.target.controlId) }\n $target = $script:AgentForgeTarget\n }\n if ($payload.action.kind -eq 'focus') { $target.SetFocus() }\n elseif ($payload.action.kind -eq 'click') {\n $pattern = $null\n $invoked = $false\n try {\n if ($target.TryGetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern, [ref]$pattern)) {\n $pattern.Invoke()\n $invoked = $true\n }\n } catch {}\n if (-not $invoked) {\n if ($controlHandle -le 0) { throw 'Control does not support InvokePattern' }\n try {\n Add-Type @'\nusing System;\nusing System.Runtime.InteropServices;\npublic static class AgentForgeNativeClick {\n [DllImport(\"user32.dll\", CharSet=CharSet.Unicode, SetLastError=true)]\n public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam, uint flags, uint timeout, out IntPtr result);\n}\n'@\n $sendResult = [IntPtr]::Zero\n $callResult = [AgentForgeNativeClick]::SendMessageTimeout([IntPtr]$controlHandle, 0x00F5, [IntPtr]::Zero, [IntPtr]::Zero, 0x0002, 3000, [ref]$sendResult)\n if ($callResult -eq [IntPtr]::Zero) { throw 'Target click failed' }\n } catch { throw 'Target click failed' }\n }\n } elseif ($payload.action.kind -eq 'type') {\n $pattern = $null\n $set = $false\n try {\n if ($target.TryGetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern, [ref]$pattern)) {\n $pattern.SetValue([string]$payload.action.text)\n $set = $true\n }\n } catch {}\n if (-not $set) {\n $legacy = $null\n try {\n if ($target.TryGetCurrentPattern([System.Windows.Automation.LegacyIAccessiblePattern]::Pattern, [ref]$legacy)) {\n $legacy.SetValue([string]$payload.action.text)\n $set = $true\n }\n } catch {}\n }\n if (-not $set) {\n if ($controlHandle -le 0) { throw 'Control does not support ValuePattern or LegacyIAccessiblePattern' }\n try {\n Add-Type @'\nusing System;\nusing System.Runtime.InteropServices;\npublic static class AgentForgeNativeText {\n [DllImport(\"user32.dll\", CharSet=CharSet.Unicode, SetLastError=true)]\n public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, IntPtr wParam, string lParam, uint flags, uint timeout, out IntPtr result);\n}\n'@\n $sendResult = [IntPtr]::Zero\n $callResult = [AgentForgeNativeText]::SendMessageTimeout([IntPtr]$controlHandle, 0x000C, [IntPtr]::Zero, [string]$payload.action.text, 0x0002, 3000, [ref]$sendResult)\n if ($callResult -eq [IntPtr]::Zero -or $sendResult -eq [IntPtr]::Zero) { throw 'Target text update failed' }\n } catch { throw 'Target text update failed' }\n }\n } elseif ($payload.action.kind -eq 'key') {\n $keyText = [string]$payload.action.key\n $hasSendKeysSyntax = $keyText -match '[+%^{}]'\n if (-not $hasSendKeysSyntax -and $controlHandle -gt 0) {\n # Plain text with a real control handle: deliver synchronously through\n # SendMessage WM_CHAR. Queued input (SendInput / WScript SendKeys) never\n # reaches GUI windows in non-interactive or remote desktop sessions, while\n # synchronous SendMessage does — the same path that type (WM_SETTEXT) and\n # click (BM_CLICK) already use. Keyboard focus is not required.\n Add-Type @'\nusing System;\nusing System.Runtime.InteropServices;\npublic static class AgentForgeNativeKey {\n [DllImport(\"user32.dll\", SetLastError=true)] public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);\n}\n'@\n foreach ($ch in $keyText.ToCharArray()) {\n [AgentForgeNativeKey]::SendMessage([IntPtr]$controlHandle, 0x0102, [IntPtr][char]$ch, [IntPtr]::Zero) | Out-Null\n }\n } else {\n if ($payload.target.controlId) { $target.SetFocus() }\n $wshell = New-Object -ComObject WScript.Shell\n $wshell.SendKeys($keyText)\n }\n } elseif ($payload.action.kind -eq 'scroll') {\n Add-Type @'\nusing System;\nusing System.Runtime.InteropServices;\npublic static class AgentForgeNativeScroll {\n [DllImport(\"user32.dll\", SetLastError=true)]\n public static extern bool SetCursorPos(int X, int Y);\n [DllImport(\"user32.dll\")]\n public static extern void mouse_event(uint dwFlags, int dx, int dy, int dwData, UIntPtr dwExtraInfo);\n}\n'@\n $direction = [string]$payload.action.text\n $directionMatch = [regex]::Match($direction, '^(up|down)(?::(\\\\d+))?$')\n if (-not $directionMatch.Success) { throw 'Scroll direction must be up or down with optional :lines' }\n $lines = if ($directionMatch.Groups[2].Value) { [int]$directionMatch.Groups[2].Value } else { 3 }\n if ($lines -lt 1 -or $lines -gt 100) { throw 'Scroll lines must be between 1 and 100' }\n try {\n $rect = $target.Current.BoundingRectangle\n $centerX = [int]($rect.X + $rect.Width / 2)\n $centerY = [int]($rect.Y + $rect.Height / 2)\n [AgentForgeNativeScroll]::SetCursorPos($centerX, $centerY) | Out-Null\n } catch {}\n $wheelDelta = 120 * $lines\n if ($direction.StartsWith('down')) { $wheelDelta = -$wheelDelta }\n [AgentForgeNativeScroll]::mouse_event(0x0800, 0, 0, $wheelDelta, [UIntPtr]::Zero)\n } else { throw ('Action kind is not supported by UI Automation: ' + [string]$payload.action.kind) }\n [pscustomobject]@{ ok=$true } | ConvertTo-Json -Compress\n} catch {\n $message = [string]$_.Exception.Message\n $code = if ($message -match 'Window handle is no longer available|Control was not found') { 'stale_target' } else { 'action_failed' }\n $reason = if ($message -match 'ValuePattern|LegacyIAccessiblePattern') { 'value_pattern_unavailable' } elseif ($message -match 'InvokePattern') { 'invoke_pattern_unavailable' } elseif ($message -match 'SetFocus') { 'focus_failed' } else { 'uia_action_exception' }\n [pscustomobject]@{ ok=$false; code=$code; reason=$reason } | ConvertTo-Json -Compress\n}`;\n\nconst parseJson = (stdout: string): Record<string, unknown> | undefined => {\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(stdout.trim());\n\t\treturn typeof parsed === \"object\" && parsed !== null ? (parsed as Record<string, unknown>) : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n};\n\nexport class WindowsUiAutomationDriver implements DesktopWindowsDriverV1 {\n\treadonly driverId = \"windows-uia\";\n\treadonly driverVersion = \"1.0.0\";\n\tprivate readonly command: string;\n\tprivate readonly timeoutMs: number;\n\tprivate readonly platform: NodeJS.Platform;\n\tprivate readonly spawn: UiAutomationSpawn;\n\tprivate readonly sessions = new Map<string, DesktopSessionV1>();\n\tprivate readonly targets = new Map<\n\t\tstring,\n\t\t{\n\t\t\treadonly sessionId: string;\n\t\t\treadonly observationId: string;\n\t\t\treadonly windowHandle: number;\n\t\t\treadonly controlWindowHandle?: number;\n\t\t\treadonly controlId?: string;\n\t\t}\n\t>();\n\tprivate readonly sessionTargetKeys = new Map<string, Set<string>>();\n\tprivate readonly latestObservationIds = new Map<string, string>();\n\tprivate readonly observedSessions = new Set<string>();\n\tprivate nextSessionId = 1;\n\tprivate nextObservationId = 1;\n\n\tconstructor(options: WindowsUiAutomationDriverOptions = {}) {\n\t\tthis.command = options.command ?? \"powershell.exe\";\n\t\tthis.timeoutMs = options.timeoutMs ?? 15_000;\n\t\tthis.platform = options.platform ?? process.platform;\n\t\tthis.spawn =\n\t\t\toptions.spawnSync ??\n\t\t\t((command, args, spawnOptions) =>\n\t\t\t\tnodeSpawnSync(command, args, spawnOptions) as unknown as UiAutomationSpawnResult);\n\t}\n\n\tprobe(): DesktopWindowsProbeV1 {\n\t\tif (this.platform !== \"win32\") {\n\t\t\treturn {\n\t\t\t\tavailable: false,\n\t\t\t\tplatform: this.platform,\n\t\t\t\tdriver: \"windows-uia\",\n\t\t\t\treason: \"Windows UI Automation requires win32\",\n\t\t\t};\n\t\t}\n\t\tconst result = this.run(QUERY_SCRIPT);\n\t\tif (result === undefined)\n\t\t\treturn {\n\t\t\t\tavailable: false,\n\t\t\t\tplatform: this.platform,\n\t\t\t\tdriver: \"windows-uia\",\n\t\t\t\treason: \"PowerShell or UIAutomationClient is unavailable\",\n\t\t\t};\n\t\treturn result.available === true\n\t\t\t? {\n\t\t\t\t\tavailable: true,\n\t\t\t\t\tplatform: this.platform,\n\t\t\t\t\tdriver: \"windows-uia\",\n\t\t\t\t\tcapabilities: READONLY_CAPABILITIES,\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tavailable: false,\n\t\t\t\t\tplatform: this.platform,\n\t\t\t\t\tdriver: \"windows-uia\",\n\t\t\t\t\treason: \"UI Automation probe failed\",\n\t\t\t\t};\n\t}\n\n\topenSession(request: DesktopSessionRequestV1): DesktopSessionResultV1 {\n\t\tconst probe = this.probe();\n\t\tif (!probe.available)\n\t\t\treturn {\n\t\t\t\tstatus: \"unsupported\",\n\t\t\t\terror: error(\n\t\t\t\t\t\"platform_unavailable\",\n\t\t\t\t\tprobe.reason ?? \"Windows UI Automation is unavailable\",\n\t\t\t\t\t\"platform-unavailable\",\n\t\t\t\t),\n\t\t\t};\n\t\tconst session = detachedFreeze({\n\t\t\tversion: 1 as const,\n\t\t\tsessionId: `desktop-windows-session-${this.nextSessionId++}`,\n\t\t\tisolation: request.isolation,\n\t\t\tstatus: \"ready\" as const,\n\t\t\t...(request.applicationId === undefined ? {} : { applicationId: request.applicationId }),\n\t\t});\n\t\tthis.sessions.set(session.sessionId, session);\n\t\treturn { status: \"succeeded\", session };\n\t}\n\n\tobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\tconst session = this.sessions.get(sessionId);\n\t\tif (session === undefined) return this.failed(\"observe\", \"session_not_found\", \"Session does not exist\");\n\t\tif (session.status === \"stopped\") return this.cancelled(\"observe\", \"Session has been stopped\");\n\t\tconst raw = this.run(QUERY_SCRIPT);\n\t\tif (raw === undefined || raw.available !== true) {\n\t\t\tthis.invalidateObservation(sessionId);\n\t\t\treturn unsupported(\"observe\", \"Windows UI Automation observation failed\");\n\t\t}\n\t\treturn this.toObservation(session, raw as unknown as RawSnapshot);\n\t}\n\n\texecute(request: DesktopOperationRequestV1): DesktopOperationResultV1 {\n\t\tconst session = this.sessions.get(request.sessionId);\n\t\tif (session === undefined) return this.failed(request.operationId, \"session_not_found\", \"Session does not exist\");\n\t\tif (session.status === \"stopped\") return this.cancelled(request.operationId, \"Session has been stopped\");\n\t\tif (requiresDesktopUserConfirmation(request.action.kind))\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\toperationId: request.operationId,\n\t\t\t\tstatus: \"needs_user\",\n\t\t\t\tsummary: `User confirmation required for ${request.action.kind}`,\n\t\t\t\terror: error(\"needs_user\", `The ${request.action.kind} action is paused for the user`, request.action.kind),\n\t\t\t};\n\t\tif (!SUPPORTED_ACTIONS.includes(request.action.kind))\n\t\t\treturn unsupported(\n\t\t\t\trequest.operationId,\n\t\t\t\t\"The selected Windows UI Automation adapter does not support this action\",\n\t\t\t\t\"action-unsupported\",\n\t\t\t);\n\t\tif (request.action.target === undefined)\n\t\t\treturn this.failed(request.operationId, \"target_required\", \"This desktop action requires an observed target\");\n\t\tif (request.action.kind === \"type\" && typeof request.action.text !== \"string\")\n\t\t\treturn this.failed(request.operationId, \"text_required\", \"Type action requires text\");\n\t\tif (request.action.kind === \"key\" && typeof request.action.key !== \"string\")\n\t\t\treturn this.failed(request.operationId, \"key_required\", \"Key action requires a key sequence\");\n\t\tif (request.action.kind === \"scroll\" && typeof request.action.text !== \"string\")\n\t\t\treturn this.failed(request.operationId, \"direction_required\", \"Scroll action requires an up/down direction\");\n\t\tconst postconditions = request.postconditions ?? [];\n\t\tif (postconditions.length > 0 && request.reobserveAfterAction === false)\n\t\t\treturn this.failed(\n\t\t\t\trequest.operationId,\n\t\t\t\t\"reobserve_required\",\n\t\t\t\t\"Postconditions require an action re-observation\",\n\t\t\t);\n\t\tconst target = this.resolveTarget(request.sessionId, request.action.target);\n\t\tif (target === undefined)\n\t\t\treturn this.failed(\n\t\t\t\trequest.operationId,\n\t\t\t\tthis.observedSessions.has(request.sessionId) ? \"stale_target\" : \"target_not_found\",\n\t\t\t\tthis.observedSessions.has(request.sessionId)\n\t\t\t\t\t? \"Target identity is stale; reobserve the desktop before retrying\"\n\t\t\t\t\t: \"Target identity was not observed by UI Automation\",\n\t\t\t);\n\t\tconst raw = this.run(ACTION_SCRIPT, { action: request.action, target: target ?? {} });\n\t\tif (raw === undefined) return unsupported(request.operationId, \"Windows UI Automation action failed\");\n\t\tif (raw.ok !== true) {\n\t\t\tif (raw.code === \"stale_target\") {\n\t\t\t\tthis.invalidateObservation(request.sessionId);\n\t\t\t\treturn this.failed(\n\t\t\t\t\trequest.operationId,\n\t\t\t\t\t\"stale_target\",\n\t\t\t\t\t\"Target identity is stale; reobserve the desktop before retrying\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst causeCode =\n\t\t\t\ttypeof raw.reason === \"string\" &&\n\t\t\t\t[\n\t\t\t\t\t\"value_pattern_unavailable\",\n\t\t\t\t\t\"invoke_pattern_unavailable\",\n\t\t\t\t\t\"focus_failed\",\n\t\t\t\t\t\"uia_action_exception\",\n\t\t\t\t].includes(raw.reason)\n\t\t\t\t\t? raw.reason\n\t\t\t\t\t: \"uia_action_exception\";\n\t\t\treturn this.failed(request.operationId, \"action_failed\", \"UI Automation action failed\", causeCode);\n\t\t}\n\t\tconst observation = request.reobserveAfterAction === false ? undefined : this.observe(request.sessionId);\n\t\tconst verification = this.verifyPostconditions(postconditions, observation);\n\t\tif (postconditions.length > 0 && verification.status === \"unknown\")\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\toperationId: request.operationId,\n\t\t\t\tstatus: \"failed\",\n\t\t\t\tsummary: \"Desktop action completed but could not be re-observed\",\n\t\t\t\tverification,\n\t\t\t\terror: error(\n\t\t\t\t\t\"verification_unavailable\",\n\t\t\t\t\t\"The requested desktop state could not be re-observed\",\n\t\t\t\t\t\"reobserve-failed\",\n\t\t\t\t),\n\t\t\t};\n\t\tif (verification.status === \"failed\")\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\toperationId: request.operationId,\n\t\t\t\tstatus: \"failed\",\n\t\t\t\tsummary: \"Desktop action completed but a postcondition failed\",\n\t\t\t\t...(observation !== undefined && \"observationId\" in observation ? { observation } : {}),\n\t\t\t\tverification,\n\t\t\t\terror: error(\"postcondition_failed\", \"The requested desktop state was not observed\", \"verification-failed\"),\n\t\t\t};\n\t\treturn {\n\t\t\tversion: 1,\n\t\t\toperationId: request.operationId,\n\t\t\tstatus: \"succeeded\",\n\t\t\tsummary: \"Desktop action completed through Windows UI Automation\",\n\t\t\t...(observation !== undefined && \"observationId\" in observation ? { observation } : {}),\n\t\t\tverification,\n\t\t};\n\t}\n\n\tstop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1 {\n\t\tconst session = this.sessions.get(sessionId);\n\t\tif (session === undefined) return this.failed(\"stop\", \"session_not_found\", \"Session does not exist\");\n\t\tconst stopped = detachedFreeze({ ...session, status: \"stopped\" as const });\n\t\tthis.sessions.set(sessionId, stopped);\n\t\tthis.invalidateObservation(sessionId);\n\t\treturn stopped;\n\t}\n\n\tprivate run(script: string, payload?: unknown): Record<string, unknown> | undefined {\n\t\tif (this.platform !== \"win32\") return undefined;\n\t\tconst env: NodeJS.ProcessEnv = {};\n\t\tfor (const key of [\"SystemRoot\", \"WINDIR\", \"PATH\", \"PATHEXT\", \"ComSpec\", \"TEMP\", \"TMP\"] as const) {\n\t\t\tconst value = process.env[key];\n\t\t\tif (value !== undefined) env[key] = value;\n\t\t}\n\t\tif (payload !== undefined) env.AGENT_FORGE_UIA_INPUT = JSON.stringify(payload);\n\t\tconst result = this.spawn(\n\t\t\tthis.command,\n\t\t\t[\"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script],\n\t\t\t{ encoding: \"utf8\", timeout: this.timeoutMs, windowsHide: true, env },\n\t\t);\n\t\tif (result.error !== undefined || result.status !== 0) return undefined;\n\t\treturn parseJson(result.stdout);\n\t}\n\n\tprivate toObservation(session: DesktopSessionV1, raw: RawSnapshot): DesktopObservationV1 {\n\t\tthis.invalidateObservation(session.sessionId);\n\t\tconst observationId = `desktop-windows-observation-${this.nextObservationId++}`;\n\t\tconst targetKeys = new Set<string>();\n\t\tthis.sessionTargetKeys.set(session.sessionId, targetKeys);\n\t\tconst windows: DesktopWindowIdentityV1[] = [];\n\t\tfor (const item of raw.windows ?? []) {\n\t\t\tif (!Number.isFinite(item.handle)) continue;\n\t\t\tconst handle = Number(item.handle);\n\t\t\tconst windowId = `window-${handle}`;\n\t\t\twindows.push({\n\t\t\t\tversion: 1,\n\t\t\t\twindowId,\n\t\t\t\tappId: `process:${Number(item.processId ?? 0)}`,\n\t\t\t\ttitle: item.name ?? \"\",\n\t\t\t\tbounds: rectangle(item),\n\t\t\t});\n\t\t}\n\t\tconst visibleWindows =\n\t\t\tsession.applicationId === undefined\n\t\t\t\t? windows\n\t\t\t\t: windows.filter((window) => window.appId === session.applicationId);\n\t\tfor (const window of visibleWindows) {\n\t\t\tconst handle = Number(/^window-(\\d+)$/.exec(window.windowId)?.[1]);\n\t\t\tif (!Number.isFinite(handle)) continue;\n\t\t\tconst key = this.targetKey(session.sessionId, \"window\", window.windowId);\n\t\t\tthis.targets.set(key, { sessionId: session.sessionId, observationId, windowHandle: handle });\n\t\t\ttargetKeys.add(key);\n\t\t}\n\t\tconst controls: DesktopControlIdentityV1[] = [];\n\t\tfor (const item of raw.controls ?? []) {\n\t\t\tif (!Number.isFinite(item.windowHandle)) continue;\n\t\t\tconst windowId = `window-${Number(item.windowHandle)}`;\n\t\t\tif (!visibleWindows.some((window) => window.windowId === windowId)) continue;\n\t\t\tconst controlId = `${windowId}:${item.controlId ?? \"control\"}`;\n\t\t\tconst key = this.targetKey(session.sessionId, \"control\", controlId);\n\t\t\tthis.targets.set(key, {\n\t\t\t\tsessionId: session.sessionId,\n\t\t\t\tobservationId,\n\t\t\t\twindowHandle: Number(item.windowHandle),\n\t\t\t\t...(Number.isFinite(item.nativeWindowHandle) && Number(item.nativeWindowHandle) > 0\n\t\t\t\t\t? { controlWindowHandle: Number(item.nativeWindowHandle) }\n\t\t\t\t\t: {}),\n\t\t\t\tcontrolId: item.controlId,\n\t\t\t});\n\t\t\ttargetKeys.add(key);\n\t\t\tcontrols.push({\n\t\t\t\tversion: 1,\n\t\t\t\tcontrolId,\n\t\t\t\twindowId,\n\t\t\t\trole: item.role ?? \"\",\n\t\t\t\tname: item.name ?? \"\",\n\t\t\t\tbounds: rectangle(item),\n\t\t\t\tenabled: item.enabled !== false,\n\t\t\t\t...(typeof item.value === \"string\" && item.value.length > 0 ? { value: item.value } : {}),\n\t\t\t});\n\t\t}\n\t\tconst focusedControl = raw.focusedControl;\n\t\tconst focusedWindowId = Number.isFinite(focusedControl?.windowHandle)\n\t\t\t? `window-${Number(focusedControl?.windowHandle)}`\n\t\t\t: undefined;\n\t\tconst focusedControlId =\n\t\t\tfocusedWindowId === undefined || typeof focusedControl?.controlId !== \"string\"\n\t\t\t\t? undefined\n\t\t\t\t: `${focusedWindowId}:${focusedControl.controlId}`;\n\t\tconst observation = detachedFreeze({\n\t\t\tversion: 1 as const,\n\t\t\tsessionId: session.sessionId,\n\t\t\tobservationId,\n\t\t\tsource: \"windows-uia\" as const,\n\t\t\twindows: visibleWindows,\n\t\t\tcontrols,\n\t\t\t...(focusedControlId !== undefined && controls.some((control) => control.controlId === focusedControlId)\n\t\t\t\t? { focusedControlId }\n\t\t\t\t: {}),\n\t\t});\n\t\tthis.latestObservationIds.set(session.sessionId, observationId);\n\t\tthis.observedSessions.add(session.sessionId);\n\t\treturn observation;\n\t}\n\n\tprivate resolveTarget(\n\t\tsessionId: string,\n\t\ttarget: DesktopTargetRefV1,\n\t): { windowHandle: number; controlWindowHandle?: number; controlId?: string } | undefined {\n\t\tconst identity = target.kind === \"control\" ? target.controlId : target.windowId;\n\t\tconst record = this.targets.get(this.targetKey(sessionId, target.kind, identity));\n\t\tif (\n\t\t\trecord === undefined ||\n\t\t\trecord.sessionId !== sessionId ||\n\t\t\trecord.observationId !== this.latestObservationIds.get(sessionId)\n\t\t)\n\t\t\treturn undefined;\n\t\treturn {\n\t\t\twindowHandle: record.windowHandle,\n\t\t\t...(record.controlWindowHandle === undefined ? {} : { controlWindowHandle: record.controlWindowHandle }),\n\t\t\t...(record.controlId === undefined ? {} : { controlId: record.controlId }),\n\t\t};\n\t}\n\n\tprivate targetKey(sessionId: string, kind: DesktopTargetRefV1[\"kind\"], identity: string): string {\n\t\treturn `${sessionId}:${kind}:${identity}`;\n\t}\n\n\tprivate invalidateObservation(sessionId: string): void {\n\t\tfor (const key of this.sessionTargetKeys.get(sessionId) ?? []) this.targets.delete(key);\n\t\tthis.sessionTargetKeys.delete(sessionId);\n\t\tthis.latestObservationIds.delete(sessionId);\n\t}\n\n\tprivate verifyPostconditions(\n\t\tpostconditions: readonly DesktopPostconditionV1[],\n\t\tobservation: DesktopObservationV1 | DesktopOperationResultV1 | undefined,\n\t): DesktopVerificationV1 {\n\t\tif (postconditions.length === 0) return { status: \"passed\", checks: [] };\n\t\tif (observation === undefined || !(\"observationId\" in observation))\n\t\t\treturn { status: \"unknown\", checks: [\"observation-unavailable\"] };\n\t\tconst checks: string[] = [];\n\t\tfor (const condition of postconditions) {\n\t\t\tconst passed = this.checkPostcondition(condition, observation);\n\t\t\tchecks.push(`${condition.kind}:${passed ? \"passed\" : \"failed\"}`);\n\t\t\tif (!passed) return { status: \"failed\", checks };\n\t\t}\n\t\treturn { status: \"passed\", checks };\n\t}\n\n\tprivate checkPostcondition(condition: DesktopPostconditionV1, observation: DesktopObservationV1): boolean {\n\t\tif (condition.kind === \"window-exists\")\n\t\t\treturn observation.windows.some((window) => window.windowId === condition.windowId);\n\t\tif (condition.kind === \"control-exists\")\n\t\t\treturn observation.controls.some((control) => control.controlId === condition.controlId);\n\t\tif (condition.kind === \"focused\") return observation.focusedControlId === condition.controlId;\n\t\tconst target = condition.target;\n\t\tif (target.kind === \"window\") {\n\t\t\tconst window = observation.windows.find((item) => item.windowId === target.windowId);\n\t\t\tif (window === undefined || condition.property !== \"name\") return false;\n\t\t\treturn window.title === condition.expected;\n\t\t}\n\t\tconst control = observation.controls.find((item) => {\n\t\t\tif (item.controlId !== target.controlId) return false;\n\t\t\treturn target.windowId === undefined || item.windowId === target.windowId;\n\t\t});\n\t\tif (control === undefined) return false;\n\t\tif (condition.property === \"enabled\") return control.enabled === condition.expected;\n\t\tif (condition.property === \"name\") return control.name === condition.expected;\n\t\treturn control.value === condition.expected;\n\t}\n\n\tprivate failed(operationId: string, code: string, message: string, causeCode?: string): DesktopOperationResultV1 {\n\t\treturn { version: 1, operationId, status: \"failed\", summary: message, error: error(code, message, causeCode) };\n\t}\n\tprivate cancelled(operationId: string, message: string): DesktopOperationResultV1 {\n\t\treturn {\n\t\t\tversion: 1,\n\t\t\toperationId,\n\t\t\tstatus: \"cancelled\",\n\t\t\tsummary: message,\n\t\t\terror: error(\"cancelled\", message, \"stopped\"),\n\t\t};\n\t}\n}\n\n/** Adapter facade that adds replay and the needs_user gate around a Windows driver. */\nexport class DesktopWindowsAdapter implements DesktopAdapterV1 {\n\treadonly adapterId = \"desktop-windows-uia\";\n\treadonly adapterVersion = \"1.0.0\";\n\treadonly capabilities = READONLY_CAPABILITIES;\n\tprivate readonly driver: DesktopWindowsDriverV1;\n\tconstructor(driver: DesktopWindowsDriverV1 = new WindowsUiAutomationDriver()) {\n\t\tthis.driver = driver;\n\t}\n\tprobe(): DesktopWindowsProbeV1 {\n\t\treturn this.driver.probe();\n\t}\n\topenSession(request: DesktopSessionRequestV1): DesktopSessionResultV1 {\n\t\treturn this.driver.openSession(request);\n\t}\n\tobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.observe(sessionId);\n\t}\n\treobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.observe(sessionId);\n\t}\n\texecute(request: DesktopOperationRequestV1): DesktopOperationResultV1 {\n\t\tif (requiresDesktopUserConfirmation(request.action.kind)) {\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\toperationId: request.operationId,\n\t\t\t\tstatus: \"needs_user\",\n\t\t\t\tsummary: `User confirmation required for ${request.action.kind}`,\n\t\t\t\terror: error(\"needs_user\", `The ${request.action.kind} action is paused for the user`, request.action.kind),\n\t\t\t};\n\t\t}\n\t\treturn this.driver.execute(request);\n\t}\n\tstop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.stop(sessionId);\n\t}\n\treplay(request: DesktopReplayRequestV1): DesktopReplayResultV1 {\n\t\tconst mismatchedOperationId = findDesktopReplaySessionMismatch(request);\n\t\tif (mismatchedOperationId !== undefined)\n\t\t\treturn detachedFreeze({\n\t\t\t\tversion: 1 as const,\n\t\t\t\tstatus: \"failed\" as const,\n\t\t\t\tresults: [\n\t\t\t\t\t{\n\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\toperationId: mismatchedOperationId,\n\t\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\t\tsummary: \"Replay operation session does not match the replay session\",\n\t\t\t\t\t\terror: error(\n\t\t\t\t\t\t\t\"session_mismatch\",\n\t\t\t\t\t\t\t\"Replay operation session does not match the replay session\",\n\t\t\t\t\t\t\t\"replay-session\",\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\t\tconst results: DesktopOperationResultV1[] = [];\n\t\tfor (const operation of request.operations) {\n\t\t\tconst result = this.execute(operation);\n\t\t\tresults.push(result);\n\t\t\tif (request.stopOnFailure && result.status !== \"succeeded\")\n\t\t\t\treturn detachedFreeze({\n\t\t\t\t\tversion: 1 as const,\n\t\t\t\t\tstatus:\n\t\t\t\t\t\tresult.status === \"cancelled\"\n\t\t\t\t\t\t\t? \"cancelled\"\n\t\t\t\t\t\t\t: result.status === \"needs_user\"\n\t\t\t\t\t\t\t\t? \"needs_user\"\n\t\t\t\t\t\t\t\t: \"failed\",\n\t\t\t\t\tresults,\n\t\t\t\t});\n\t\t}\n\t\treturn detachedFreeze({ version: 1 as const, status: \"succeeded\" as const, results });\n\t}\n}\n"]}
1
+ {"version":3,"file":"windows-uia-adapter.d.ts","sourceRoot":"","sources":["../src/windows-uia-adapter.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACX,gBAAgB,EAChB,qBAAqB,EAGrB,oBAAoB,EACpB,yBAAyB,EACzB,wBAAwB,EAExB,sBAAsB,EACtB,qBAAqB,EACrB,yBAAyB,EACzB,yBAAyB,EACzB,uBAAuB,EACvB,sBAAsB,EACtB,gBAAgB,EAIhB,sBAAsB,EACtB,qBAAqB,EACrB,MAAM,YAAY,CAAC;AA+BpB,KAAK,uBAAuB,GAAG;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,iBAAiB,GAAG,CACxB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE;IACR,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5B,KACG,uBAAuB,CAAC;AAE7B,MAAM,WAAW,gCAAgC;IAChD,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,iFAAiF;IACjF,QAAQ,CAAC,SAAS,CAAC,EAAE,iBAAiB,CAAC;IACvC,sFAAsF;IACtF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CACpC;AA8bD,qBAAa,yBAA0B,YAAW,sBAAsB,EAAE,yBAAyB;IAClG,QAAQ,CAAC,QAAQ,iBAAiB;IAClC,QAAQ,CAAC,aAAa,WAAW;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAChE,OAAO,CAAC,QAAQ,CAAC,OAAO,CASpB;IACJ,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAkC;IACpE,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAA6B;IAClE,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;IACtD,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,iBAAiB,CAAK;IAE9B,YAAY,OAAO,GAAE,gCAAqC,EAQzD;IAED,KAAK,IAAI,qBAAqB,CA8B7B;IAED,WAAW,CAAC,OAAO,EAAE,uBAAuB,GAAG,sBAAsB,CAoBpE;IAED,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,wBAAwB,CAU1E;IAED,OAAO,CAAC,OAAO,EAAE,yBAAyB,GAAG,wBAAwB,CAuEpE;IAED,OAAO,CAAC,cAAc;IA8DtB,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG,wBAAwB,CAOnE;IAED,wFAAwF;IACxF,UAAU,IAAI,yBAAyB,CA8DtC;IAED,OAAO,CAAC,GAAG;IAuBX,OAAO,CAAC,aAAa;IAiFrB,OAAO,CAAC,aAAa;IAoBrB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,qBAAqB;IAM7B,OAAO,CAAC,oBAAoB;IAgB5B,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,MAAM;IAGd,OAAO,CAAC,SAAS;CASjB;AAED,uFAAuF;AACvF,qBAAa,qBAAsB,YAAW,gBAAgB;IAC7D,QAAQ,CAAC,SAAS,yBAAyB;IAC3C,QAAQ,CAAC,cAAc,WAAW;IAClC,QAAQ,CAAC,YAAY,mCAAyB;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAChD,YAAY,MAAM,GAAE,sBAAwD,EAE3E;IACD,KAAK,IAAI,qBAAqB,CAE7B;IACD,WAAW,CAAC,OAAO,EAAE,uBAAuB,GAAG,sBAAsB,CAEpE;IACD,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,wBAAwB,CAE1E;IACD,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,wBAAwB,CAE5E;IACD,OAAO,CAAC,OAAO,EAAE,yBAAyB,GAAG,wBAAwB,CAWpE;IACD,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG,wBAAwB,CAEnE;IACD,8GAA8G;IAC9G,UAAU,IAAI,yBAAyB,CActC;IACD,MAAM,CAAC,OAAO,EAAE,sBAAsB,GAAG,qBAAqB,CAqC7D;CACD","sourcesContent":["/**\n * Inlined from agent-forge `src/plugins/desktop/windows-uia-adapter.ts`\n * (D-075 S3 extraction) so this package keeps its dependency closure limited\n * to the plugin SDK and typebox. Semantics are unchanged: the full driver\n * (query + action PowerShell scripts) is carried completely even though this\n * observe package only exposes the read-only capability surface. This file is\n * a deliberate copy shared (not imported) with\n * `plugins/desktop-windows-observe/src/windows-uia-adapter.ts`; keep both\n * copies in sync.\n */\nimport { spawnSync as nodeSpawnSync } from \"node:child_process\";\nimport { detachedFreeze } from \"@agent-forge/plugin-sdk\";\nimport type {\n\tDesktopAdapterV1,\n\tDesktopCapabilityIdV1,\n\tDesktopControlIdentityV1,\n\tDesktopErrorV1,\n\tDesktopObservationV1,\n\tDesktopOperationRequestV1,\n\tDesktopOperationResultV1,\n\tDesktopPostconditionV1,\n\tDesktopReplayRequestV1,\n\tDesktopReplayResultV1,\n\tDesktopScreenshotDriverV1,\n\tDesktopScreenshotResultV1,\n\tDesktopSessionRequestV1,\n\tDesktopSessionResultV1,\n\tDesktopSessionV1,\n\tDesktopTargetRefV1,\n\tDesktopVerificationV1,\n\tDesktopWindowIdentityV1,\n\tDesktopWindowsDriverV1,\n\tDesktopWindowsProbeV1,\n} from \"./types.ts\";\nimport { findDesktopReplaySessionMismatch, requiresDesktopUserConfirmation } from \"./types.ts\";\n\ntype RawWindow = {\n\thandle?: number;\n\tprocessId?: number;\n\tname?: string;\n\tx?: number;\n\ty?: number;\n\twidth?: number;\n\theight?: number;\n};\ntype RawControl = {\n\twindowHandle?: number;\n\tnativeWindowHandle?: number;\n\tcontrolId?: string;\n\tname?: string;\n\trole?: string;\n\tenabled?: boolean;\n\tvalue?: string;\n\tx?: number;\n\ty?: number;\n\twidth?: number;\n\theight?: number;\n};\ntype RawSnapshot = {\n\twindows?: RawWindow[];\n\tcontrols?: RawControl[];\n\tfocusedControl?: { windowHandle?: number; controlId?: string };\n};\n\ntype UiAutomationSpawnResult = {\n\treadonly error?: Error;\n\treadonly status: number | null;\n\treadonly stdout: string;\n};\n\ntype UiAutomationSpawn = (\n\tcommand: string,\n\targs: readonly string[],\n\toptions: {\n\t\treadonly encoding: \"utf8\";\n\t\treadonly timeout: number;\n\t\treadonly windowsHide: boolean;\n\t\treadonly env: NodeJS.ProcessEnv;\n\t\t/** Raised only for the screenshot script so multi-MiB PNG payloads survive. */\n\t\treadonly maxBuffer?: number;\n\t},\n) => UiAutomationSpawnResult;\n\nexport interface WindowsUiAutomationDriverOptions {\n\t/** Override the PowerShell executable for a brokered host or test harness. */\n\treadonly command?: string;\n\treadonly timeoutMs?: number;\n\t/** Injectable host boundary for deterministic tests and brokered deployments. */\n\treadonly spawnSync?: UiAutomationSpawn;\n\t/** Override the platform reported to the driver; intended for host adapters/tests. */\n\treadonly platform?: NodeJS.Platform;\n}\n\nconst READONLY_CAPABILITIES: readonly DesktopCapabilityIdV1[] = [\"desktop-session\", \"desktop-uia\"];\nconst SUPPORTED_ACTIONS: readonly DesktopOperationRequestV1[\"action\"][\"kind\"][] = [\n\t\"focus\",\n\t\"click\",\n\t\"type\",\n\t\"key\",\n\t\"scroll\",\n];\n\n/** Contract limit: a delivered screenshot must stay at or below 4 MiB decoded. */\nconst DESKTOP_SCREENSHOT_MAX_DECODED_BYTES = 4 * 1024 * 1024;\n/** Spawn buffer headroom above the contract limit so the limit is enforced here, not by maxBuffer. */\nconst SCREENSHOT_SPAWN_MAX_BUFFER_BYTES = 16 * 1024 * 1024;\n/** Screens wider than this are scaled down proportionally before PNG encoding. */\nconst DESKTOP_SCREENSHOT_MAX_WIDTH_PX = 1280;\n\nconst error = (code: string, message: string, causeCode?: string): DesktopErrorV1 => ({\n\tversion: 1,\n\tcode,\n\tmessage,\n\t...(causeCode === undefined ? {} : { causeCode }),\n});\n\nconst unsupported = (\n\toperationId: string,\n\treason: string,\n\tcauseCode = \"platform-unavailable\",\n): DesktopOperationResultV1 => ({\n\tversion: 1,\n\toperationId,\n\tstatus: \"unsupported\",\n\tsummary: reason,\n\terror: error(causeCode === \"action-unsupported\" ? \"action_unsupported\" : \"platform_unavailable\", reason, causeCode),\n});\n\nconst rectangle = (raw: { x?: number; y?: number; width?: number; height?: number }) => ({\n\tx: Number.isFinite(raw.x) ? Number(raw.x) : 0,\n\ty: Number.isFinite(raw.y) ? Number(raw.y) : 0,\n\twidth: Number.isFinite(raw.width) ? Number(raw.width) : 0,\n\theight: Number.isFinite(raw.height) ? Number(raw.height) : 0,\n});\n\nconst screenshotDimension = (value: unknown): number =>\n\ttypeof value === \"number\" && Number.isFinite(value) && Number.isInteger(value) && value > 0 ? value : 0;\n\nconst QUERY_SCRIPT = `\ntry {\n Add-Type -AssemblyName UIAutomationClient -ErrorAction Stop\n Add-Type -AssemblyName UIAutomationTypes -ErrorAction Stop\n function SafeInt($value) {\n $number = [double]$value\n if ([double]::IsNaN($number) -or [double]::IsInfinity($number)) { return 0 }\n return [int][math]::Round($number)\n }\n function Get-ControlId($node, [int]$index) {\n $automationId = [string]$node.Current.AutomationId\n $name = [string]$node.Current.Name\n $role = [string]$node.Current.ControlType.ProgrammaticName\n if ($automationId.Length -gt 0) { return $automationId }\n if ($name.Length -gt 0) { return $role + ':' + $name }\n return $role + ':' + $index\n }\n $root = [System.Windows.Automation.AutomationElement]::RootElement\n $windowWalker = [System.Windows.Automation.TreeWalker]::ControlViewWalker\n $windows = [System.Collections.Generic.List[object]]::new()\n $controls = [System.Collections.Generic.List[object]]::new()\n $focusedMatches = [System.Collections.Generic.List[object]]::new()\n $focused = $null\n try { $focused = [System.Windows.Automation.AutomationElement]::FocusedElement } catch {}\n $maxWindows = 256\n $maxControlsPerWindow = 1000\n $maxControlsTotal = 10000\n $maxDepth = 32\n function Add-Descendants($node, [int64]$windowHandle, [int]$depth, [ref]$count) {\n if ($null -eq $node -or $depth -gt $maxDepth -or $count.Value -ge $maxControlsPerWindow -or $controls.Count -ge $maxControlsTotal) { return }\n $index = $count.Value\n $count.Value = $count.Value + 1\n $childRect = $node.Current.BoundingRectangle\n $controlId = Get-ControlId $node $index\n $value = $null\n try {\n $pattern = $null\n if ($node.TryGetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern, [ref]$pattern)) { $value = [string]$pattern.Current.Value }\n } catch {}\n $name = [string]$node.Current.Name\n $nativeWindowHandle = [int64]$node.Current.NativeWindowHandle\n if ($null -eq $value -and $name.Length -gt 0) { $value = $name }\n $controls.Add([pscustomobject]@{ windowHandle=$windowHandle; nativeWindowHandle=$nativeWindowHandle; controlId=$controlId; name=$name; role=[string]$node.Current.ControlType.ProgrammaticName; enabled=[bool]$node.Current.IsEnabled; value=$value; x=(SafeInt $childRect.X); y=(SafeInt $childRect.Y); width=(SafeInt $childRect.Width); height=(SafeInt $childRect.Height) })\n try {\n if ($null -ne $focused -and $node.Equals($focused)) { $focusedMatches.Add([pscustomobject]@{ windowHandle=$windowHandle; controlId=$controlId }) }\n } catch {}\n $child = $windowWalker.GetFirstChild($node)\n while ($null -ne $child -and $count.Value -lt $maxControlsPerWindow) {\n Add-Descendants $child $windowHandle ($depth + 1) $count\n $child = $windowWalker.GetNextSibling($child)\n }\n }\n $window = $windowWalker.GetFirstChild($root)\n $windowCount = 0\n while ($null -ne $window -and $windowCount -lt $maxWindows) {\n $handle = [int64]$window.Current.NativeWindowHandle\n if ($handle -ne 0) {\n $rect = $window.Current.BoundingRectangle\n $processId = [int]$window.Current.ProcessId\n $windows.Add([pscustomobject]@{ handle=$handle; processId=$processId; name=[string]$window.Current.Name; x=(SafeInt $rect.X); y=(SafeInt $rect.Y); width=(SafeInt $rect.Width); height=(SafeInt $rect.Height) })\n $windowCount++\n $child = $windowWalker.GetFirstChild($window)\n $count = 0\n while ($null -ne $child -and $count -lt $maxControlsPerWindow -and $controls.Count -lt $maxControlsTotal) {\n Add-Descendants $child $handle 1 ([ref]$count)\n $child = $windowWalker.GetNextSibling($child)\n }\n }\n $window = $windowWalker.GetNextSibling($window)\n }\n $focusedControl = if ($focusedMatches.Count -gt 0) { $focusedMatches[0] } else { $null }\n [pscustomobject]@{ windows=@($windows); controls=@($controls); focusedControl=$focusedControl; available=$true } | ConvertTo-Json -Compress -Depth 8\n} catch {\n [pscustomobject]@{ available=$false; error=[string]$_.Exception.Message } | ConvertTo-Json -Compress -Depth 5\n}`;\n\nconst ACTION_SCRIPT = `\ntry {\n Add-Type -AssemblyName UIAutomationClient -ErrorAction Stop\n Add-Type -AssemblyName UIAutomationTypes -ErrorAction Stop\n $payload = $env:AGENT_FORGE_UIA_INPUT | ConvertFrom-Json\n $handle = [int64]$payload.target.windowHandle\n $window = [System.Windows.Automation.AutomationElement]::FromHandle([IntPtr]$handle)\n if ($null -eq $window) { throw 'Window handle is no longer available' }\n $walker = [System.Windows.Automation.TreeWalker]::ControlViewWalker\n $target = $window\n $script:AgentForgeTarget = $null\n if ($null -ne $payload.target.controlId) {\n $controlHandle = [int64]$payload.target.controlWindowHandle\n if ($controlHandle -gt 0) {\n try {\n $candidate = [System.Windows.Automation.AutomationElement]::FromHandle([IntPtr]$controlHandle)\n if ($null -ne $candidate) {\n $candidateAutomationId = [string]$candidate.Current.AutomationId\n $candidateName = [string]$candidate.Current.Name\n $candidateRole = [string]$candidate.Current.ControlType.ProgrammaticName\n $candidateId = if ($candidateAutomationId.Length -gt 0) { $candidateAutomationId } elseif ($candidateName.Length -gt 0) { $candidateRole + ':' + $candidateName } else { $null }\n if ($candidateId -eq [string]$payload.target.controlId) { $script:AgentForgeTarget = $candidate }\n }\n } catch {}\n }\n $maxDepth = 32\n $maxVisited = 1000\n function Find-Descendant($node, [int]$depth, [ref]$visited) {\n if ($null -eq $node -or $depth -gt $maxDepth -or $visited.Value -ge $maxVisited -or $null -ne $script:AgentForgeTarget) { return }\n $visited.Value = $visited.Value + 1\n $automationId = [string]$node.Current.AutomationId\n $name = [string]$node.Current.Name\n $role = [string]$node.Current.ControlType.ProgrammaticName\n $id = if ($automationId.Length -gt 0) { $automationId } elseif ($name.Length -gt 0) { $role + ':' + $name } else { $role + ':' + ($visited.Value - 1) }\n if ($id -eq [string]$payload.target.controlId) { $script:AgentForgeTarget = $node; return }\n $child = $walker.GetFirstChild($node)\n while ($null -ne $child -and $visited.Value -lt $maxVisited -and $null -eq $script:AgentForgeTarget) {\n Find-Descendant $child ($depth + 1) $visited\n $child = $walker.GetNextSibling($child)\n }\n }\n $visited = 0\n if ($null -eq $script:AgentForgeTarget) {\n $node = $walker.GetFirstChild($window)\n while ($null -ne $node -and $visited -lt $maxVisited -and $null -eq $script:AgentForgeTarget) {\n Find-Descendant $node 1 ([ref]$visited)\n $node = $walker.GetNextSibling($node)\n }\n }\n if ($null -eq $script:AgentForgeTarget) { throw ('Control was not found: ' + [string]$payload.target.controlId) }\n $target = $script:AgentForgeTarget\n }\n if ($payload.action.kind -eq 'focus') { $target.SetFocus() }\n elseif ($payload.action.kind -eq 'click') {\n $pattern = $null\n $invoked = $false\n try {\n if ($target.TryGetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern, [ref]$pattern)) {\n $pattern.Invoke()\n $invoked = $true\n }\n } catch {}\n if (-not $invoked) {\n if ($controlHandle -le 0) { throw 'Control does not support InvokePattern' }\n try {\n Add-Type @'\nusing System;\nusing System.Runtime.InteropServices;\npublic static class AgentForgeNativeClick {\n [DllImport(\"user32.dll\", CharSet=CharSet.Unicode, SetLastError=true)]\n public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam, uint flags, uint timeout, out IntPtr result);\n}\n'@\n $sendResult = [IntPtr]::Zero\n $callResult = [AgentForgeNativeClick]::SendMessageTimeout([IntPtr]$controlHandle, 0x00F5, [IntPtr]::Zero, [IntPtr]::Zero, 0x0002, 3000, [ref]$sendResult)\n if ($callResult -eq [IntPtr]::Zero) { throw 'Target click failed' }\n } catch { throw 'Target click failed' }\n }\n } elseif ($payload.action.kind -eq 'type') {\n $pattern = $null\n $set = $false\n try {\n if ($target.TryGetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern, [ref]$pattern)) {\n $pattern.SetValue([string]$payload.action.text)\n $set = $true\n }\n } catch {}\n if (-not $set) {\n $legacy = $null\n try {\n if ($target.TryGetCurrentPattern([System.Windows.Automation.LegacyIAccessiblePattern]::Pattern, [ref]$legacy)) {\n $legacy.SetValue([string]$payload.action.text)\n $set = $true\n }\n } catch {}\n }\n if (-not $set) {\n if ($controlHandle -le 0) { throw 'Control does not support ValuePattern or LegacyIAccessiblePattern' }\n try {\n Add-Type @'\nusing System;\nusing System.Runtime.InteropServices;\npublic static class AgentForgeNativeText {\n [DllImport(\"user32.dll\", CharSet=CharSet.Unicode, SetLastError=true)]\n public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, IntPtr wParam, string lParam, uint flags, uint timeout, out IntPtr result);\n}\n'@\n $sendResult = [IntPtr]::Zero\n $callResult = [AgentForgeNativeText]::SendMessageTimeout([IntPtr]$controlHandle, 0x000C, [IntPtr]::Zero, [string]$payload.action.text, 0x0002, 3000, [ref]$sendResult)\n if ($callResult -eq [IntPtr]::Zero -or $sendResult -eq [IntPtr]::Zero) { throw 'Target text update failed' }\n } catch { throw 'Target text update failed' }\n }\n } elseif ($payload.action.kind -eq 'key') {\n # Real keyboard-queue input: keybd_event posts to the same queue as the\n # physical keyboard. The native-file-picker flow depends on this — the\n # dialog is focused but invisible to UIA and not addressable by window\n # messages sent to the owner window. Named keys and Ctrl/Alt/Shift\n # combinations go through virtual-key codes; anything else must be a\n # single character.\n try {\n Add-Type @'\nusing System;\nusing System.Runtime.InteropServices;\npublic static class AgentForgeNativeKeyQueue {\n [DllImport(\"user32.dll\")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);\n}\n'@\n } catch {}\n $vkMap = @{\n 'enter'=0x0D; 'return'=0x0D; 'tab'=0x09; 'escape'=0x1B; 'esc'=0x1B;\n 'backspace'=0x08; 'delete'=0x2E; 'space'=0x20;\n 'up'=0x26; 'down'=0x28; 'left'=0x25; 'right'=0x27;\n 'arrowup'=0x26; 'arrowdown'=0x28; 'arrowleft'=0x25; 'arrowright'=0x27;\n 'home'=0x24; 'end'=0x23; 'pageup'=0x21; 'pagedown'=0x22; 'insert'=0x2D\n }\n $modMap = @{ 'ctrl'=0x11; 'control'=0x11; 'alt'=0x12; 'shift'=0x10; 'meta'=0x5B; 'win'=0x5B; 'cmd'=0x5B }\n $keyText = ([string]$payload.action.key).Trim()\n $mods = @()\n $base = $null\n foreach ($p in ($keyText -split '+')) {\n $lp = $p.ToLower()\n if ($modMap.ContainsKey($lp)) { $mods += $modMap[$lp] } else { $base = $lp }\n }\n $vk = $null\n if ($null -ne $base -and $vkMap.ContainsKey($base)) { $vk = $vkMap[$base] }\n elseif ($null -ne $base -and $base.Length -eq 1) {\n $upper = [char]::ToUpper($base[0])\n $code = [int]$upper\n if (($code -ge 0x20 -and $code -le 0x5A) -or ($code -ge 0x30 -and $code -le 0x39)) { $vk = $code }\n }\n if ($null -eq $vk) { throw ('Unsupported key: ' + $keyText) }\n foreach ($m in $mods) { [AgentForgeNativeKeyQueue]::keybd_event([byte]$m, 0, 0, [UIntPtr]::Zero) }\n [AgentForgeNativeKeyQueue]::keybd_event([byte]$vk, 0, 0, [UIntPtr]::Zero)\n [AgentForgeNativeKeyQueue]::keybd_event([byte]$vk, 0, 2, [UIntPtr]::Zero)\n for ($i = $mods.Count - 1; $i -ge 0; $i--) { [AgentForgeNativeKeyQueue]::keybd_event([byte]$mods[$i], 0, 2, [UIntPtr]::Zero) }\n } elseif ($payload.action.kind -eq 'scroll') {\n Add-Type @'\nusing System;\nusing System.Runtime.InteropServices;\npublic static class AgentForgeNativeScroll {\n [DllImport(\"user32.dll\", SetLastError=true)]\n public static extern bool SetCursorPos(int X, int Y);\n [DllImport(\"user32.dll\")]\n public static extern void mouse_event(uint dwFlags, int dx, int dy, int dwData, UIntPtr dwExtraInfo);\n}\n'@\n $direction = [string]$payload.action.text\n $directionMatch = [regex]::Match($direction, '^(up|down)(?::(\\\\d+))?$')\n if (-not $directionMatch.Success) { throw 'Scroll direction must be up or down with optional :lines' }\n $lines = if ($directionMatch.Groups[2].Value) { [int]$directionMatch.Groups[2].Value } else { 3 }\n if ($lines -lt 1 -or $lines -gt 100) { throw 'Scroll lines must be between 1 and 100' }\n try {\n $rect = $target.Current.BoundingRectangle\n $centerX = [int]($rect.X + $rect.Width / 2)\n $centerY = [int]($rect.Y + $rect.Height / 2)\n [AgentForgeNativeScroll]::SetCursorPos($centerX, $centerY) | Out-Null\n } catch {}\n $wheelDelta = 120 * $lines\n if ($direction.StartsWith('down')) { $wheelDelta = -$wheelDelta }\n [AgentForgeNativeScroll]::mouse_event(0x0800, 0, 0, $wheelDelta, [UIntPtr]::Zero)\n } else { throw ('Action kind is not supported by UI Automation: ' + [string]$payload.action.kind) }\n [pscustomobject]@{ ok=$true } | ConvertTo-Json -Compress\n} catch {\n $message = [string]$_.Exception.Message\n $code = if ($message -match 'Window handle is no longer available|Control was not found') { 'stale_target' } else { 'action_failed' }\n $reason = if ($message -match 'ValuePattern|LegacyIAccessiblePattern') { 'value_pattern_unavailable' } elseif ($message -match 'InvokePattern') { 'invoke_pattern_unavailable' } elseif ($message -match 'SetFocus') { 'focus_failed' } else { 'uia_action_exception' }\n [pscustomobject]@{ ok=$false; code=$code; reason=$reason } | ConvertTo-Json -Compress\n}`;\n\n/**\n * Foreground (no-target) type/key: real keyboard-queue input for surfaces UIA\n * cannot see — the native file dialog's focused name box being the canonical\n * case. Named keys go through virtual-key codes with modifier support; other\n * text is typed as VK_PACKET unicode events.\n */\nconst FOREGROUND_INPUT_SCRIPT = `\ntry {\n $payload = $env:AGENT_FORGE_UIA_INPUT | ConvertFrom-Json\n try {\n Add-Type @'\nusing System;\nusing System.Runtime.InteropServices;\npublic static class AgentForgeForegroundKeyQueue {\n [DllImport(\"user32.dll\")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);\n}\n'@\n } catch {}\n $vkMap = @{\n 'enter'=0x0D; 'return'=0x0D; 'tab'=0x09; 'escape'=0x1B; 'esc'=0x1B;\n 'backspace'=0x08; 'delete'=0x2E; 'space'=0x20;\n 'up'=0x26; 'down'=0x28; 'left'=0x25; 'right'=0x27;\n 'arrowup'=0x26; 'arrowdown'=0x28; 'arrowleft'=0x25; 'arrowright'=0x27;\n 'home'=0x24; 'end'=0x23; 'pageup'=0x21; 'pagedown'=0x22; 'insert'=0x2D\n }\n $modMap = @{ 'ctrl'=0x11; 'control'=0x11; 'alt'=0x12; 'shift'=0x10; 'meta'=0x5B; 'win'=0x5B; 'cmd'=0x5B }\n if ($payload.action.kind -eq 'type') {\n foreach ($ch in ([string]$payload.action.text).ToCharArray()) {\n [AgentForgeForegroundKeyQueue]::keybd_event(0xE7, [byte][int]$ch, 0, [UIntPtr]::Zero)\n [AgentForgeForegroundKeyQueue]::keybd_event(0xE7, [byte][int]$ch, 2, [UIntPtr]::Zero)\n }\n } elseif ($payload.action.kind -eq 'key') {\n $keyText = ([string]$payload.action.key).Trim()\n $mods = @()\n $base = $null\n foreach ($p in ($keyText -split '\\\\+')) {\n $lp = $p.ToLower()\n if ($modMap.ContainsKey($lp)) { $mods += $modMap[$lp] } else { $base = $lp }\n }\n $vk = $null\n if ($null -ne $base -and $vkMap.ContainsKey($base)) { $vk = $vkMap[$base] }\n elseif ($null -ne $base -and $base.Length -eq 1) {\n $upper = [char]::ToUpper($base[0])\n $code = [int]$upper\n if (($code -ge 0x20 -and $code -le 0x5A) -or ($code -ge 0x30 -and $code -le 0x39)) { $vk = $code }\n }\n if ($null -eq $vk) { throw ('Unsupported key: ' + $keyText) }\n foreach ($m in $mods) { [AgentForgeForegroundKeyQueue]::keybd_event([byte]$m, 0, 0, [UIntPtr]::Zero) }\n [AgentForgeForegroundKeyQueue]::keybd_event([byte]$vk, 0, 0, [UIntPtr]::Zero)\n [AgentForgeForegroundKeyQueue]::keybd_event([byte]$vk, 0, 2, [UIntPtr]::Zero)\n for ($i = $mods.Count - 1; $i -ge 0; $i--) { [AgentForgeForegroundKeyQueue]::keybd_event([byte]$mods[$i], 0, 2, [UIntPtr]::Zero) }\n } else { throw 'Foreground input supports only type and key' }\n [pscustomobject]@{ ok=$true } | ConvertTo-Json -Compress\n} catch {\n [pscustomobject]@{ ok=$false; code='action_failed'; reason=[string]$_.Exception.Message } | ConvertTo-Json -Compress\n}`;\n\nconst COORDINATE_CLICK_SCRIPT = `\ntry {\n $payload = $env:AGENT_FORGE_UIA_INPUT | ConvertFrom-Json\n $x = [int]$payload.target.x\n $y = [int]$payload.target.y\n Add-Type @'\nusing System;\nusing System.Runtime.InteropServices;\npublic static class AgentForgeCoordinateClick {\n [DllImport(\"user32.dll\", SetLastError=true)]\n public static extern bool SetCursorPos(int X, int Y);\n [DllImport(\"user32.dll\")]\n public static extern void mouse_event(uint dwFlags, int dx, int dy, int dwData, UIntPtr dwExtraInfo);\n}\n'@\n if (-not [AgentForgeCoordinateClick]::SetCursorPos($x, $y)) { throw 'Coordinate click could not move the cursor' }\n [AgentForgeCoordinateClick]::mouse_event(0x0002, 0, 0, 0, [UIntPtr]::Zero)\n [AgentForgeCoordinateClick]::mouse_event(0x0004, 0, 0, 0, [UIntPtr]::Zero)\n [pscustomobject]@{ ok=$true } | ConvertTo-Json -Compress\n} catch {\n [pscustomobject]@{ ok=$false; reason='uia_action_exception' } | ConvertTo-Json -Compress\n}`;\n\nconst SCREENSHOT_SCRIPT = `\ntry {\n Add-Type @'\nusing System;\nusing System.Runtime.InteropServices;\npublic static class AgentForgeScreenDpi {\n [DllImport(\"user32.dll\")] public static extern bool SetProcessDPIAware();\n}\n'@\n [void][AgentForgeScreenDpi]::SetProcessDPIAware()\n Add-Type -AssemblyName System.Windows.Forms -ErrorAction Stop\n Add-Type -AssemblyName System.Drawing -ErrorAction Stop\n $bounds = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds\n $originalWidth = [int]$bounds.Width\n $originalHeight = [int]$bounds.Height\n if ($originalWidth -le 0 -or $originalHeight -le 0) { throw 'Primary display bounds are unavailable' }\n $width = if ($originalWidth -gt ${DESKTOP_SCREENSHOT_MAX_WIDTH_PX}) { ${DESKTOP_SCREENSHOT_MAX_WIDTH_PX} } else { $originalWidth }\n $height = [int][math]::Round([double]$originalHeight * $width / $originalWidth)\n if ($height -lt 1) { $height = 1 }\n $source = New-Object System.Drawing.Bitmap($originalWidth, $originalHeight)\n $target = $source\n try {\n $graphics = [System.Drawing.Graphics]::FromImage($source)\n try { $graphics.CopyFromScreen($bounds.X, $bounds.Y, 0, 0, [System.Drawing.Size]::new($originalWidth, $originalHeight)) } finally { $graphics.Dispose() }\n if ($width -ne $originalWidth -or $height -ne $originalHeight) {\n $target = New-Object System.Drawing.Bitmap($width, $height)\n $resized = [System.Drawing.Graphics]::FromImage($target)\n try {\n $resized.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic\n $resized.DrawImage($source, 0, 0, $width, $height)\n } finally { $resized.Dispose() }\n }\n $stream = New-Object System.IO.MemoryStream\n try {\n $target.Save($stream, [System.Drawing.Imaging.ImageFormat]::Png)\n [pscustomobject]@{ ok=$true; data=[Convert]::ToBase64String($stream.ToArray()); width=$width; height=$height; originalWidth=$originalWidth; originalHeight=$originalHeight } | ConvertTo-Json -Compress -Depth 4\n } finally { $stream.Dispose() }\n } finally {\n if (-not [object]::ReferenceEquals($target, $source)) { $target.Dispose() }\n $source.Dispose()\n }\n} catch {\n [pscustomobject]@{ ok=$false; reason='screenshot_exception' } | ConvertTo-Json -Compress -Depth 4\n}`;\n\nconst parseJson = (stdout: string): Record<string, unknown> | undefined => {\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(stdout.trim());\n\t\treturn typeof parsed === \"object\" && parsed !== null ? (parsed as Record<string, unknown>) : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n};\n\nexport class WindowsUiAutomationDriver implements DesktopWindowsDriverV1, DesktopScreenshotDriverV1 {\n\treadonly driverId = \"windows-uia\";\n\treadonly driverVersion = \"1.0.0\";\n\tprivate readonly command: string;\n\tprivate readonly timeoutMs: number;\n\tprivate readonly platform: NodeJS.Platform;\n\tprivate readonly spawn: UiAutomationSpawn;\n\tprivate readonly sessions = new Map<string, DesktopSessionV1>();\n\tprivate readonly targets = new Map<\n\t\tstring,\n\t\t{\n\t\t\treadonly sessionId: string;\n\t\t\treadonly observationId: string;\n\t\t\treadonly windowHandle: number;\n\t\t\treadonly controlWindowHandle?: number;\n\t\t\treadonly controlId?: string;\n\t\t}\n\t>();\n\tprivate readonly sessionTargetKeys = new Map<string, Set<string>>();\n\tprivate readonly latestObservationIds = new Map<string, string>();\n\tprivate readonly observedSessions = new Set<string>();\n\tprivate nextSessionId = 1;\n\tprivate nextObservationId = 1;\n\n\tconstructor(options: WindowsUiAutomationDriverOptions = {}) {\n\t\tthis.command = options.command ?? \"powershell.exe\";\n\t\tthis.timeoutMs = options.timeoutMs ?? 15_000;\n\t\tthis.platform = options.platform ?? process.platform;\n\t\tthis.spawn =\n\t\t\toptions.spawnSync ??\n\t\t\t((command, args, spawnOptions) =>\n\t\t\t\tnodeSpawnSync(command, args, spawnOptions) as unknown as UiAutomationSpawnResult);\n\t}\n\n\tprobe(): DesktopWindowsProbeV1 {\n\t\tif (this.platform !== \"win32\") {\n\t\t\treturn {\n\t\t\t\tavailable: false,\n\t\t\t\tplatform: this.platform,\n\t\t\t\tdriver: \"windows-uia\",\n\t\t\t\treason: \"Windows UI Automation requires win32\",\n\t\t\t};\n\t\t}\n\t\tconst result = this.run(QUERY_SCRIPT);\n\t\tif (result === undefined)\n\t\t\treturn {\n\t\t\t\tavailable: false,\n\t\t\t\tplatform: this.platform,\n\t\t\t\tdriver: \"windows-uia\",\n\t\t\t\treason: \"PowerShell or UIAutomationClient is unavailable\",\n\t\t\t};\n\t\treturn result.available === true\n\t\t\t? {\n\t\t\t\t\tavailable: true,\n\t\t\t\t\tplatform: this.platform,\n\t\t\t\t\tdriver: \"windows-uia\",\n\t\t\t\t\tcapabilities: READONLY_CAPABILITIES,\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tavailable: false,\n\t\t\t\t\tplatform: this.platform,\n\t\t\t\t\tdriver: \"windows-uia\",\n\t\t\t\t\treason: \"UI Automation probe failed\",\n\t\t\t\t};\n\t}\n\n\topenSession(request: DesktopSessionRequestV1): DesktopSessionResultV1 {\n\t\tconst probe = this.probe();\n\t\tif (!probe.available)\n\t\t\treturn {\n\t\t\t\tstatus: \"unsupported\",\n\t\t\t\terror: error(\n\t\t\t\t\t\"platform_unavailable\",\n\t\t\t\t\tprobe.reason ?? \"Windows UI Automation is unavailable\",\n\t\t\t\t\t\"platform-unavailable\",\n\t\t\t\t),\n\t\t\t};\n\t\tconst session = detachedFreeze({\n\t\t\tversion: 1 as const,\n\t\t\tsessionId: `desktop-windows-session-${this.nextSessionId++}`,\n\t\t\tisolation: request.isolation,\n\t\t\tstatus: \"ready\" as const,\n\t\t\t...(request.applicationId === undefined ? {} : { applicationId: request.applicationId }),\n\t\t});\n\t\tthis.sessions.set(session.sessionId, session);\n\t\treturn { status: \"succeeded\", session };\n\t}\n\n\tobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\tconst session = this.sessions.get(sessionId);\n\t\tif (session === undefined) return this.failed(\"observe\", \"session_not_found\", \"Session does not exist\");\n\t\tif (session.status === \"stopped\") return this.cancelled(\"observe\", \"Session has been stopped\");\n\t\tconst raw = this.run(QUERY_SCRIPT);\n\t\tif (raw === undefined || raw.available !== true) {\n\t\t\tthis.invalidateObservation(sessionId);\n\t\t\treturn unsupported(\"observe\", \"Windows UI Automation observation failed\");\n\t\t}\n\t\treturn this.toObservation(session, raw as unknown as RawSnapshot);\n\t}\n\n\texecute(request: DesktopOperationRequestV1): DesktopOperationResultV1 {\n\t\tconst session = this.sessions.get(request.sessionId);\n\t\tif (session === undefined) return this.failed(request.operationId, \"session_not_found\", \"Session does not exist\");\n\t\tif (session.status === \"stopped\") return this.cancelled(request.operationId, \"Session has been stopped\");\n\t\tif (requiresDesktopUserConfirmation(request.action.kind))\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\toperationId: request.operationId,\n\t\t\t\tstatus: \"needs_user\",\n\t\t\t\tsummary: `User confirmation required for ${request.action.kind}`,\n\t\t\t\terror: error(\"needs_user\", `The ${request.action.kind} action is paused for the user`, request.action.kind),\n\t\t\t};\n\t\tif (!SUPPORTED_ACTIONS.includes(request.action.kind))\n\t\t\treturn unsupported(\n\t\t\t\trequest.operationId,\n\t\t\t\t\"The selected Windows UI Automation adapter does not support this action\",\n\t\t\t\t\"action-unsupported\",\n\t\t\t);\n\t\tconst postconditions = request.postconditions ?? [];\n\t\tif (postconditions.length > 0 && request.reobserveAfterAction === false)\n\t\t\treturn this.failed(\n\t\t\t\trequest.operationId,\n\t\t\t\t\"reobserve_required\",\n\t\t\t\t\"Postconditions require an action re-observation\",\n\t\t\t);\n\t\t// Coordinate clicks address raw screen pixels; they bypass observed target\n\t\t// identity entirely and are click-only by contract.\n\t\tif (request.action.target !== undefined && request.action.target.kind === \"coordinates\") {\n\t\t\tif (request.action.kind !== \"click\")\n\t\t\t\treturn unsupported(\n\t\t\t\t\trequest.operationId,\n\t\t\t\t\t\"The selected Windows UI Automation adapter does not support this action\",\n\t\t\t\t\t\"action-unsupported\",\n\t\t\t\t);\n\t\t\tconst raw = this.run(COORDINATE_CLICK_SCRIPT, {\n\t\t\t\taction: request.action,\n\t\t\t\ttarget: request.action.target,\n\t\t\t});\n\t\t\treturn this.completeAction(request, postconditions, raw);\n\t\t}\n\t\t// No observed target: type/key go to the real keyboard queue addressed at\n\t\t// the current focus — the only path that reaches UIA-invisible surfaces\n\t\t// like the native file dialog.\n\t\tif (request.action.target === undefined) {\n\t\t\tif (request.action.kind === \"type\" && typeof request.action.text === \"string\") {\n\t\t\t\tconst raw = this.run(FOREGROUND_INPUT_SCRIPT, { action: request.action });\n\t\t\t\treturn this.completeAction(request, postconditions, raw);\n\t\t\t}\n\t\t\tif (request.action.kind === \"key\" && typeof request.action.key === \"string\") {\n\t\t\t\tconst raw = this.run(FOREGROUND_INPUT_SCRIPT, { action: request.action });\n\t\t\t\treturn this.completeAction(request, postconditions, raw);\n\t\t\t}\n\t\t\treturn this.failed(request.operationId, \"target_required\", \"This desktop action requires an observed target\");\n\t\t}\n\t\tif (request.action.kind === \"type\" && typeof request.action.text !== \"string\")\n\t\t\treturn this.failed(request.operationId, \"text_required\", \"Type action requires text\");\n\t\tif (request.action.kind === \"key\" && typeof request.action.key !== \"string\")\n\t\t\treturn this.failed(request.operationId, \"key_required\", \"Key action requires a key sequence\");\n\t\tif (request.action.kind === \"scroll\" && typeof request.action.text !== \"string\")\n\t\t\treturn this.failed(request.operationId, \"direction_required\", \"Scroll action requires an up/down direction\");\n\t\tconst target = this.resolveTarget(request.sessionId, request.action.target);\n\t\tif (target === undefined)\n\t\t\treturn this.failed(\n\t\t\t\trequest.operationId,\n\t\t\t\tthis.observedSessions.has(request.sessionId) ? \"stale_target\" : \"target_not_found\",\n\t\t\t\tthis.observedSessions.has(request.sessionId)\n\t\t\t\t\t? \"Target identity is stale; reobserve the desktop before retrying\"\n\t\t\t\t\t: \"Target identity was not observed by UI Automation\",\n\t\t\t);\n\t\tconst raw = this.run(ACTION_SCRIPT, { action: request.action, target: target ?? {} });\n\t\treturn this.completeAction(request, postconditions, raw);\n\t}\n\n\tprivate completeAction(\n\t\trequest: DesktopOperationRequestV1,\n\t\tpostconditions: readonly DesktopPostconditionV1[],\n\t\traw: Record<string, unknown> | undefined,\n\t): DesktopOperationResultV1 {\n\t\tif (raw === undefined) return unsupported(request.operationId, \"Windows UI Automation action failed\");\n\t\tif (raw.ok !== true) {\n\t\t\tif (raw.code === \"stale_target\") {\n\t\t\t\tthis.invalidateObservation(request.sessionId);\n\t\t\t\treturn this.failed(\n\t\t\t\t\trequest.operationId,\n\t\t\t\t\t\"stale_target\",\n\t\t\t\t\t\"Target identity is stale; reobserve the desktop before retrying\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst causeCode =\n\t\t\t\ttypeof raw.reason === \"string\" &&\n\t\t\t\t[\n\t\t\t\t\t\"value_pattern_unavailable\",\n\t\t\t\t\t\"invoke_pattern_unavailable\",\n\t\t\t\t\t\"focus_failed\",\n\t\t\t\t\t\"uia_action_exception\",\n\t\t\t\t].includes(raw.reason)\n\t\t\t\t\t? raw.reason\n\t\t\t\t\t: \"uia_action_exception\";\n\t\t\treturn this.failed(request.operationId, \"action_failed\", \"UI Automation action failed\", causeCode);\n\t\t}\n\t\tconst observation = request.reobserveAfterAction === false ? undefined : this.observe(request.sessionId);\n\t\tconst verification = this.verifyPostconditions(postconditions, observation);\n\t\tif (postconditions.length > 0 && verification.status === \"unknown\")\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\toperationId: request.operationId,\n\t\t\t\tstatus: \"failed\",\n\t\t\t\tsummary: \"Desktop action completed but could not be re-observed\",\n\t\t\t\tverification,\n\t\t\t\terror: error(\n\t\t\t\t\t\"verification_unavailable\",\n\t\t\t\t\t\"The requested desktop state could not be re-observed\",\n\t\t\t\t\t\"reobserve-failed\",\n\t\t\t\t),\n\t\t\t};\n\t\tif (verification.status === \"failed\")\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\toperationId: request.operationId,\n\t\t\t\tstatus: \"failed\",\n\t\t\t\tsummary: \"Desktop action completed but a postcondition failed\",\n\t\t\t\t...(observation !== undefined && \"observationId\" in observation ? { observation } : {}),\n\t\t\t\tverification,\n\t\t\t\terror: error(\"postcondition_failed\", \"The requested desktop state was not observed\", \"verification-failed\"),\n\t\t\t};\n\t\treturn {\n\t\t\tversion: 1,\n\t\t\toperationId: request.operationId,\n\t\t\tstatus: \"succeeded\",\n\t\t\tsummary: \"Desktop action completed through Windows UI Automation\",\n\t\t\t...(observation !== undefined && \"observationId\" in observation ? { observation } : {}),\n\t\t\tverification,\n\t\t};\n\t}\n\n\tstop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1 {\n\t\tconst session = this.sessions.get(sessionId);\n\t\tif (session === undefined) return this.failed(\"stop\", \"session_not_found\", \"Session does not exist\");\n\t\tconst stopped = detachedFreeze({ ...session, status: \"stopped\" as const });\n\t\tthis.sessions.set(sessionId, stopped);\n\t\tthis.invalidateObservation(sessionId);\n\t\treturn stopped;\n\t}\n\n\t/** Capture the primary display through GDI; the PNG is scaled to a 1280px width cap. */\n\tscreenshot(): DesktopScreenshotResultV1 {\n\t\tif (this.platform !== \"win32\")\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\tsource: \"windows-gdi\",\n\t\t\t\tstatus: \"unsupported\",\n\t\t\t\terror: error(\"platform_unavailable\", \"Windows GDI screen capture requires win32\", \"platform-unavailable\"),\n\t\t\t};\n\t\tconst raw = this.run(SCREENSHOT_SCRIPT, undefined, SCREENSHOT_SPAWN_MAX_BUFFER_BYTES);\n\t\tif (raw === undefined)\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\tsource: \"windows-gdi\",\n\t\t\t\tstatus: \"failed\",\n\t\t\t\terror: error(\"screenshot_failed\", \"Windows GDI screen capture failed\", \"screenshot-capture-unavailable\"),\n\t\t\t};\n\t\tif (raw.ok !== true)\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\tsource: \"windows-gdi\",\n\t\t\t\tstatus: \"failed\",\n\t\t\t\terror: error(\"screenshot_failed\", \"Windows GDI screen capture failed\", \"screenshot-exception\"),\n\t\t\t};\n\t\tconst data = typeof raw.data === \"string\" ? raw.data : \"\";\n\t\tconst dimensions = {\n\t\t\twidth: screenshotDimension(raw.width),\n\t\t\theight: screenshotDimension(raw.height),\n\t\t\toriginalWidth: screenshotDimension(raw.originalWidth),\n\t\t\toriginalHeight: screenshotDimension(raw.originalHeight),\n\t\t};\n\t\tif (Math.floor((data.length * 3) / 4) > DESKTOP_SCREENSHOT_MAX_DECODED_BYTES)\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\tsource: \"windows-gdi\",\n\t\t\t\tstatus: \"failed\",\n\t\t\t\terror: error(\n\t\t\t\t\t\"screenshot-too-large\",\n\t\t\t\t\t\"The captured screenshot exceeds the 4 MiB decoded-size limit\",\n\t\t\t\t\t\"screenshot-too-large\",\n\t\t\t\t),\n\t\t\t};\n\t\tif (data.length === 0 || Object.values(dimensions).some((value) => value <= 0))\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\tsource: \"windows-gdi\",\n\t\t\t\tstatus: \"failed\",\n\t\t\t\terror: error(\n\t\t\t\t\t\"screenshot_failed\",\n\t\t\t\t\t\"Windows GDI screen capture returned an invalid payload\",\n\t\t\t\t\t\"screenshot-invalid-payload\",\n\t\t\t\t),\n\t\t\t};\n\t\treturn {\n\t\t\tversion: 1,\n\t\t\tsource: \"windows-gdi\",\n\t\t\tstatus: \"succeeded\",\n\t\t\tmimeType: \"image/png\",\n\t\t\tdata,\n\t\t\t...dimensions,\n\t\t\tscale: dimensions.originalWidth / dimensions.width,\n\t\t\tcapturedAt: new Date().toISOString(),\n\t\t};\n\t}\n\n\tprivate run(script: string, payload?: unknown, maxBuffer?: number): Record<string, unknown> | undefined {\n\t\tif (this.platform !== \"win32\") return undefined;\n\t\tconst env: NodeJS.ProcessEnv = {};\n\t\tfor (const key of [\"SystemRoot\", \"WINDIR\", \"PATH\", \"PATHEXT\", \"ComSpec\", \"TEMP\", \"TMP\"] as const) {\n\t\t\tconst value = process.env[key];\n\t\t\tif (value !== undefined) env[key] = value;\n\t\t}\n\t\tif (payload !== undefined) env.AGENT_FORGE_UIA_INPUT = JSON.stringify(payload);\n\t\tconst result = this.spawn(\n\t\t\tthis.command,\n\t\t\t[\"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script],\n\t\t\t{\n\t\t\t\tencoding: \"utf8\",\n\t\t\t\ttimeout: this.timeoutMs,\n\t\t\t\twindowsHide: true,\n\t\t\t\tenv,\n\t\t\t\t...(maxBuffer === undefined ? {} : { maxBuffer }),\n\t\t\t},\n\t\t);\n\t\tif (result.error !== undefined || result.status !== 0) return undefined;\n\t\treturn parseJson(result.stdout);\n\t}\n\n\tprivate toObservation(session: DesktopSessionV1, raw: RawSnapshot): DesktopObservationV1 {\n\t\tthis.invalidateObservation(session.sessionId);\n\t\tconst observationId = `desktop-windows-observation-${this.nextObservationId++}`;\n\t\tconst targetKeys = new Set<string>();\n\t\tthis.sessionTargetKeys.set(session.sessionId, targetKeys);\n\t\tconst windows: DesktopWindowIdentityV1[] = [];\n\t\tfor (const item of raw.windows ?? []) {\n\t\t\tif (!Number.isFinite(item.handle)) continue;\n\t\t\tconst handle = Number(item.handle);\n\t\t\tconst windowId = `window-${handle}`;\n\t\t\twindows.push({\n\t\t\t\tversion: 1,\n\t\t\t\twindowId,\n\t\t\t\tappId: `process:${Number(item.processId ?? 0)}`,\n\t\t\t\ttitle: item.name ?? \"\",\n\t\t\t\tbounds: rectangle(item),\n\t\t\t});\n\t\t}\n\t\tconst visibleWindows =\n\t\t\tsession.applicationId === undefined\n\t\t\t\t? windows\n\t\t\t\t: windows.filter((window) => window.appId === session.applicationId);\n\t\tfor (const window of visibleWindows) {\n\t\t\tconst handle = Number(/^window-(\\d+)$/.exec(window.windowId)?.[1]);\n\t\t\tif (!Number.isFinite(handle)) continue;\n\t\t\tconst key = this.targetKey(session.sessionId, \"window\", window.windowId);\n\t\t\tthis.targets.set(key, { sessionId: session.sessionId, observationId, windowHandle: handle });\n\t\t\ttargetKeys.add(key);\n\t\t}\n\t\tconst controls: DesktopControlIdentityV1[] = [];\n\t\tfor (const item of raw.controls ?? []) {\n\t\t\tif (!Number.isFinite(item.windowHandle)) continue;\n\t\t\tconst windowId = `window-${Number(item.windowHandle)}`;\n\t\t\tif (!visibleWindows.some((window) => window.windowId === windowId)) continue;\n\t\t\tconst controlId = `${windowId}:${item.controlId ?? \"control\"}`;\n\t\t\tconst key = this.targetKey(session.sessionId, \"control\", controlId);\n\t\t\tthis.targets.set(key, {\n\t\t\t\tsessionId: session.sessionId,\n\t\t\t\tobservationId,\n\t\t\t\twindowHandle: Number(item.windowHandle),\n\t\t\t\t...(Number.isFinite(item.nativeWindowHandle) && Number(item.nativeWindowHandle) > 0\n\t\t\t\t\t? { controlWindowHandle: Number(item.nativeWindowHandle) }\n\t\t\t\t\t: {}),\n\t\t\t\tcontrolId: item.controlId,\n\t\t\t});\n\t\t\ttargetKeys.add(key);\n\t\t\tcontrols.push({\n\t\t\t\tversion: 1,\n\t\t\t\tcontrolId,\n\t\t\t\twindowId,\n\t\t\t\trole: item.role ?? \"\",\n\t\t\t\tname: item.name ?? \"\",\n\t\t\t\tbounds: rectangle(item),\n\t\t\t\tenabled: item.enabled !== false,\n\t\t\t\t...(typeof item.value === \"string\" && item.value.length > 0 ? { value: item.value } : {}),\n\t\t\t});\n\t\t}\n\t\tconst focusedControl = raw.focusedControl;\n\t\tconst focusedWindowId = Number.isFinite(focusedControl?.windowHandle)\n\t\t\t? `window-${Number(focusedControl?.windowHandle)}`\n\t\t\t: undefined;\n\t\tconst focusedControlId =\n\t\t\tfocusedWindowId === undefined || typeof focusedControl?.controlId !== \"string\"\n\t\t\t\t? undefined\n\t\t\t\t: `${focusedWindowId}:${focusedControl.controlId}`;\n\t\tconst observation = detachedFreeze({\n\t\t\tversion: 1 as const,\n\t\t\tsessionId: session.sessionId,\n\t\t\tobservationId,\n\t\t\tsource: \"windows-uia\" as const,\n\t\t\twindows: visibleWindows,\n\t\t\tcontrols,\n\t\t\t...(focusedControlId !== undefined && controls.some((control) => control.controlId === focusedControlId)\n\t\t\t\t? { focusedControlId }\n\t\t\t\t: {}),\n\t\t});\n\t\tthis.latestObservationIds.set(session.sessionId, observationId);\n\t\tthis.observedSessions.add(session.sessionId);\n\t\treturn observation;\n\t}\n\n\tprivate resolveTarget(\n\t\tsessionId: string,\n\t\ttarget: DesktopTargetRefV1,\n\t): { windowHandle: number; controlWindowHandle?: number; controlId?: string } | undefined {\n\t\tif (target.kind === \"coordinates\") return undefined;\n\t\tconst identity = target.kind === \"control\" ? target.controlId : target.windowId;\n\t\tconst record = this.targets.get(this.targetKey(sessionId, target.kind, identity));\n\t\tif (\n\t\t\trecord === undefined ||\n\t\t\trecord.sessionId !== sessionId ||\n\t\t\trecord.observationId !== this.latestObservationIds.get(sessionId)\n\t\t)\n\t\t\treturn undefined;\n\t\treturn {\n\t\t\twindowHandle: record.windowHandle,\n\t\t\t...(record.controlWindowHandle === undefined ? {} : { controlWindowHandle: record.controlWindowHandle }),\n\t\t\t...(record.controlId === undefined ? {} : { controlId: record.controlId }),\n\t\t};\n\t}\n\n\tprivate targetKey(sessionId: string, kind: DesktopTargetRefV1[\"kind\"], identity: string): string {\n\t\treturn `${sessionId}:${kind}:${identity}`;\n\t}\n\n\tprivate invalidateObservation(sessionId: string): void {\n\t\tfor (const key of this.sessionTargetKeys.get(sessionId) ?? []) this.targets.delete(key);\n\t\tthis.sessionTargetKeys.delete(sessionId);\n\t\tthis.latestObservationIds.delete(sessionId);\n\t}\n\n\tprivate verifyPostconditions(\n\t\tpostconditions: readonly DesktopPostconditionV1[],\n\t\tobservation: DesktopObservationV1 | DesktopOperationResultV1 | undefined,\n\t): DesktopVerificationV1 {\n\t\tif (postconditions.length === 0) return { status: \"passed\", checks: [] };\n\t\tif (observation === undefined || !(\"observationId\" in observation))\n\t\t\treturn { status: \"unknown\", checks: [\"observation-unavailable\"] };\n\t\tconst checks: string[] = [];\n\t\tfor (const condition of postconditions) {\n\t\t\tconst passed = this.checkPostcondition(condition, observation);\n\t\t\tchecks.push(`${condition.kind}:${passed ? \"passed\" : \"failed\"}`);\n\t\t\tif (!passed) return { status: \"failed\", checks };\n\t\t}\n\t\treturn { status: \"passed\", checks };\n\t}\n\n\tprivate checkPostcondition(condition: DesktopPostconditionV1, observation: DesktopObservationV1): boolean {\n\t\tif (condition.kind === \"window-exists\")\n\t\t\treturn observation.windows.some((window) => window.windowId === condition.windowId);\n\t\tif (condition.kind === \"control-exists\")\n\t\t\treturn observation.controls.some((control) => control.controlId === condition.controlId);\n\t\tif (condition.kind === \"focused\") return observation.focusedControlId === condition.controlId;\n\t\tconst target = condition.target;\n\t\tif (target.kind === \"window\") {\n\t\t\tconst window = observation.windows.find((item) => item.windowId === target.windowId);\n\t\t\tif (window === undefined || condition.property !== \"name\") return false;\n\t\t\treturn window.title === condition.expected;\n\t\t}\n\t\tif (target.kind !== \"control\") return false;\n\t\tconst control = observation.controls.find((item) => {\n\t\t\tif (item.controlId !== target.controlId) return false;\n\t\t\treturn target.windowId === undefined || item.windowId === target.windowId;\n\t\t});\n\t\tif (control === undefined) return false;\n\t\tif (condition.property === \"enabled\") return control.enabled === condition.expected;\n\t\tif (condition.property === \"name\") return control.name === condition.expected;\n\t\treturn control.value === condition.expected;\n\t}\n\n\tprivate failed(operationId: string, code: string, message: string, causeCode?: string): DesktopOperationResultV1 {\n\t\treturn { version: 1, operationId, status: \"failed\", summary: message, error: error(code, message, causeCode) };\n\t}\n\tprivate cancelled(operationId: string, message: string): DesktopOperationResultV1 {\n\t\treturn {\n\t\t\tversion: 1,\n\t\t\toperationId,\n\t\t\tstatus: \"cancelled\",\n\t\t\tsummary: message,\n\t\t\terror: error(\"cancelled\", message, \"stopped\"),\n\t\t};\n\t}\n}\n\n/** Adapter facade that adds replay and the needs_user gate around a Windows driver. */\nexport class DesktopWindowsAdapter implements DesktopAdapterV1 {\n\treadonly adapterId = \"desktop-windows-uia\";\n\treadonly adapterVersion = \"1.0.0\";\n\treadonly capabilities = READONLY_CAPABILITIES;\n\tprivate readonly driver: DesktopWindowsDriverV1;\n\tconstructor(driver: DesktopWindowsDriverV1 = new WindowsUiAutomationDriver()) {\n\t\tthis.driver = driver;\n\t}\n\tprobe(): DesktopWindowsProbeV1 {\n\t\treturn this.driver.probe();\n\t}\n\topenSession(request: DesktopSessionRequestV1): DesktopSessionResultV1 {\n\t\treturn this.driver.openSession(request);\n\t}\n\tobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.observe(sessionId);\n\t}\n\treobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.observe(sessionId);\n\t}\n\texecute(request: DesktopOperationRequestV1): DesktopOperationResultV1 {\n\t\tif (requiresDesktopUserConfirmation(request.action.kind)) {\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\toperationId: request.operationId,\n\t\t\t\tstatus: \"needs_user\",\n\t\t\t\tsummary: `User confirmation required for ${request.action.kind}`,\n\t\t\t\terror: error(\"needs_user\", `The ${request.action.kind} action is paused for the user`, request.action.kind),\n\t\t\t};\n\t\t}\n\t\treturn this.driver.execute(request);\n\t}\n\tstop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.stop(sessionId);\n\t}\n\t/** Delegates to the driver when it implements DesktopScreenshotDriverV1; structured unsupported otherwise. */\n\tscreenshot(): DesktopScreenshotResultV1 {\n\t\tconst driver = this.driver as DesktopWindowsDriverV1 & Partial<DesktopScreenshotDriverV1>;\n\t\tif (typeof driver.screenshot !== \"function\")\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\tsource: \"windows-gdi\",\n\t\t\t\tstatus: \"unsupported\",\n\t\t\t\terror: error(\n\t\t\t\t\t\"driver_unavailable\",\n\t\t\t\t\t\"The selected desktop driver does not support screenshots\",\n\t\t\t\t\t\"screenshot-driver-unavailable\",\n\t\t\t\t),\n\t\t\t};\n\t\treturn driver.screenshot();\n\t}\n\treplay(request: DesktopReplayRequestV1): DesktopReplayResultV1 {\n\t\tconst mismatchedOperationId = findDesktopReplaySessionMismatch(request);\n\t\tif (mismatchedOperationId !== undefined)\n\t\t\treturn detachedFreeze({\n\t\t\t\tversion: 1 as const,\n\t\t\t\tstatus: \"failed\" as const,\n\t\t\t\tresults: [\n\t\t\t\t\t{\n\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\toperationId: mismatchedOperationId,\n\t\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\t\tsummary: \"Replay operation session does not match the replay session\",\n\t\t\t\t\t\terror: error(\n\t\t\t\t\t\t\t\"session_mismatch\",\n\t\t\t\t\t\t\t\"Replay operation session does not match the replay session\",\n\t\t\t\t\t\t\t\"replay-session\",\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\t\tconst results: DesktopOperationResultV1[] = [];\n\t\tfor (const operation of request.operations) {\n\t\t\tconst result = this.execute(operation);\n\t\t\tresults.push(result);\n\t\t\tif (request.stopOnFailure && result.status !== \"succeeded\")\n\t\t\t\treturn detachedFreeze({\n\t\t\t\t\tversion: 1 as const,\n\t\t\t\t\tstatus:\n\t\t\t\t\t\tresult.status === \"cancelled\"\n\t\t\t\t\t\t\t? \"cancelled\"\n\t\t\t\t\t\t\t: result.status === \"needs_user\"\n\t\t\t\t\t\t\t\t? \"needs_user\"\n\t\t\t\t\t\t\t\t: \"failed\",\n\t\t\t\t\tresults,\n\t\t\t\t});\n\t\t}\n\t\treturn detachedFreeze({ version: 1 as const, status: \"succeeded\" as const, results });\n\t}\n}\n"]}