@wippy-fe/vite-plugin 0.0.64 → 0.0.66

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/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../../@wippy-fe--shared/src/attention.ts","../../@wippy-fe--shared/src/attention-inspection.ts","../../@wippy-fe--shared/src/attention-relay-codec.ts","../../@wippy-fe--shared/src/surface.ts","../../@wippy-fe--shared/src/visibility.ts","../../@wippy-fe--shared/src/index.ts","../src/page-plugin.ts","../src/shared.ts","../src/component-plugin.ts","../src/index.ts"],"sourcesContent":["export const CONTEXT_ATTACHMENTS_MAX_COUNT = 8\nexport const CONTEXT_ATTACHMENTS_MAX_BYTES = 32 * 1024\nexport const CONTEXT_ATTACHMENT_MAX_BYTES = 32 * 1024\nexport const ATTENTION_CONTEXT_KIND = 'wippy.attention'\nexport const ATTENTION_CONTEXT_VERSION = 1\nexport const ATTENTION_CONTEXT_SCHEMA = 'wippy.attention.v1'\nexport const ATTENTION_CONTEXT_V2_VERSION = 2\nexport const ATTENTION_CONTEXT_V2_SCHEMA = 'wippy.attention.v2'\nexport const ATTENTION_CONTEXT_V3_VERSION = 3\nexport const ATTENTION_CONTEXT_V3_SCHEMA = 'wippy.attention.v3'\nexport const ATTENTION_CONTEXT_V4_VERSION = 4\nexport const ATTENTION_CONTEXT_V4_SCHEMA = 'wippy.attention.v4'\nexport const ATTENTION_V2_PREFERRED_CONTENT_BYTES = 12 * 1024\nexport const ATTENTION_V2_MAX_ENVELOPE_BYTES = 16 * 1024\nexport const ATTENTION_V3_MAX_ENVELOPE_BYTES = 16 * 1024\nexport const ATTENTION_MAX_EXPANDED_BYTES = 256 * 1024\nexport const ATTENTION_V2_MAX_DICTIONARY = 4128\nexport const ATTENTION_V2_MAX_MEMBERSHIPS = 16384\nexport const ATTENTION_RELAY_SCHEMA = 'wippy.attention.relay.v1'\nexport const ATTENTION_CAPABILITY_SCHEMA = 'wippy.attention.capability.v1'\nexport const ATTENTION_RUNTIME_SCHEMA = 'wippy.attention.runtime.v1'\nexport const ATTENTION_UI_ACTION_SCHEMA = 'wippy.ui-action.v1'\n/** Longest UI action lifetime the Session broker issues (`expires_at - created_at`). */\nexport const ATTENTION_UI_ACTION_MAX_TTL_MS = 120_000\nexport const ATTENTION_VISUAL_CONTEXT_KIND = 'wippy.attention.visual'\nexport const ATTENTION_VISUAL_CONTEXT_SCHEMA = 'wippy.attention.visual.v1'\nexport const ATTENTION_VISUAL_CONTEXT_VERSION = 1\nexport const ATTENTION_MAX_QUERY_POINTS = 4096\nexport const ATTENTION_MAX_CANDIDATES = 128\nexport const ATTENTION_MAX_PATH_SEGMENTS = 32\nexport const ATTENTION_MAX_POINT_OMISSIONS = 32\nexport const ATTENTION_MAX_SNAPSHOT_OMISSIONS = 128\nexport const ATTENTION_VISUAL_MAX_BYTES = 1024 * 1024\nexport const ATTENTION_VISUAL_MAX_HEIGHT_PX = 2048\nexport const ATTENTION_VISUAL_MAX_PIXELS = 4_194_304\nexport const ATTENTION_VISUAL_MAX_TIME_MS = 10_000\nexport const ATTENTION_VISUAL_MAX_WIDTH_PX = 2048\n\nexport type JsonPrimitive = boolean | number | string | null\nexport type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }\n\nexport interface AttentionConfig {\n enabled?: boolean\n messageContext?: {\n enabled?: boolean\n defaultInclude?: boolean\n }\n agentActions?: {\n enabled?: boolean\n requireConfirmation?: boolean\n }\n visualCapture?: {\n enabled?: boolean\n maxBytes?: number\n maxHeightPx?: number\n maxPixels?: number\n maxTimeMs?: number\n maxWidthPx?: number\n }\n sampling?: {\n radiusCssPx?: number\n stepCssPx?: number\n }\n privacy?: {\n text?: 'safe' | 'none'\n }\n}\n\nexport interface ContextAttachmentEnvelope {\n attachment_id: string\n kind: string\n version: number\n created_at: string\n expires_at?: string\n content_type: 'application/json'\n content_bytes: number\n content_hash: `sha256:${string}`\n content: string\n}\n\nexport interface ContextAttachmentInput<TPayload extends JsonValue> {\n attachment_id: string\n kind: string\n version: number\n created_at: string\n expires_at?: string\n payload: TPayload\n}\n\nexport type AttentionRealmKind\n = | 'host'\n | 'panel'\n | 'artifact'\n | 'page'\n | 'iframe'\n | 'web-fragment'\n | 'web-component'\n | 'shadow-root'\n | 'element'\n\nexport interface AttentionRect {\n x: number\n y: number\n width: number\n height: number\n}\n\nexport interface AttentionPoint {\n x: number\n y: number\n}\n\nexport interface AttentionQueryPoint extends AttentionPoint {\n point_id: string\n}\n\nexport interface AttentionTransform {\n matrix: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number]\n convention: 'dommatrix-column-major'\n direction: 'local-to-parent'\n}\n\nexport interface AttentionCoordinateSpace {\n kind: 'host-viewport'\n width: number\n height: number\n device_pixel_ratio: number\n}\n\nexport interface AttentionPathSegment {\n kind: AttentionRealmKind\n mount_id: string\n generation: number\n label?: string\n panel_id?: string\n surface_id?: string\n artifact_id?: string\n page_id?: string\n package_id?: string\n tag_name?: string\n selector_hint?: string\n frame_origin?: string\n rect?: AttentionRect\n clip_rect?: AttentionRect\n local_to_parent?: AttentionTransform\n coordinate_quality?: 'exact' | 'approximate'\n}\n\nexport interface AttentionTargetSummary {\n role?: string\n name?: string\n text?: string\n value?: string\n state?: Record<string, JsonPrimitive>\n}\n\nexport interface AttentionCandidate {\n target_id: string\n path: AttentionPathSegment[]\n rect: AttentionRect\n clip_rect?: AttentionRect\n sample_point_ids: string[]\n occluded: boolean\n summary: AttentionTargetSummary\n provenance?: {\n geometry_source: 'document' | 'physical-host'\n runtime_source: 'document' | 'iframe-realm' | 'fragment-realm'\n }\n action_ref?: AttentionTargetRefV1\n}\n\nexport interface AttentionObservedEvent {\n event_id: string\n sequence: number\n type: 'pointermove' | 'pointerdown' | 'pointerup' | 'click' | 'touchstart' | 'touchend'\n observed_at: string\n realm_time_ms: number\n point: AttentionPoint\n pointer_id?: number\n pointer_type?: 'mouse' | 'pen' | 'touch' | 'unknown'\n buttons?: number\n pointer_capture?: boolean\n candidate_ids: string[]\n}\n\nexport interface AttentionFocus {\n event_id: string\n sequence: number\n focused_at: string\n realm_time_ms: number\n candidate_id?: string\n path: AttentionPathSegment[]\n summary: AttentionTargetSummary\n}\n\nexport type AttentionSelectionUnknownReason = 'unavailable' | 'privacy' | 'detached' | 'unsupported'\n\nexport interface AttentionSelectionSelectedState {\n state: 'selected'\n origin_sequence: number\n origin_mount: AttentionMountRef\n selection: AttentionSelection\n}\n\nexport interface AttentionSelectionClearedState {\n state: 'cleared'\n origin_sequence: number\n origin_mount: AttentionMountRef\n reason: 'user-cleared' | 'detached' | 'navigation' | 'privacy' | 'disconnected' | 'revoked'\n cleared_at?: string\n}\n\nexport interface AttentionSelectionUnknownState {\n state: 'unknown'\n reason: AttentionSelectionUnknownReason\n}\n\nexport type AttentionSelectionState = AttentionSelectionSelectedState | AttentionSelectionClearedState | AttentionSelectionUnknownState\n\nexport interface AttentionSelectionRange {\n rect: AttentionRect\n coordinate_space: AttentionMountRef | 'host-viewport'\n}\n\nexport interface AttentionSelection {\n selection_id: string\n selected_at: string\n kind: 'text'\n collapsed: false\n direction: 'forward' | 'backward' | 'none'\n text: string\n anchor_path: AttentionPathSegment[]\n focus_path: AttentionPathSegment[]\n ranges: AttentionSelectionRange[]\n}\n\nexport function attentionSelectionSemanticKey(state: AttentionSelectionState): string {\n if (state.state !== 'selected')\n return JSON.stringify(state)\n const semanticPath = (path: AttentionPathSegment[]) => path.map(({\n rect: _rect,\n clip_rect: _clipRect,\n local_to_parent: _localToParent,\n coordinate_quality: _coordinateQuality,\n ...segment\n }) => segment)\n return JSON.stringify({\n state: state.state,\n origin_sequence: state.origin_sequence,\n origin_mount: state.origin_mount,\n selection_id: state.selection.selection_id,\n selected_at: state.selection.selected_at,\n kind: state.selection.kind,\n collapsed: state.selection.collapsed,\n direction: state.selection.direction,\n text: state.selection.text,\n anchor_path: semanticPath(state.selection.anchor_path),\n focus_path: semanticPath(state.selection.focus_path),\n })\n}\n\n/** Internal relay state. A departed realm cannot decide whether the Host focus is excluded. */\nexport interface AttentionFocusState {\n changed_at: string\n focus: AttentionFocus | null\n retained: boolean\n}\n\nexport interface AttentionObservationBatchV1 {\n received_at: string\n pointer?: AttentionObservedEvent\n focus?: AttentionFocus\n focus_state?: AttentionFocusState\n selection?: AttentionSelection\n recent_events: AttentionObservedEvent[]\n}\n\nexport type AttentionOmissionReason\n = | 'candidate-budget'\n | 'capability-unavailable'\n | 'child-disconnected'\n | 'child-timeout'\n | 'clipped'\n | 'depth-limit'\n | 'excluded'\n | 'navigation'\n | 'non-invertible-transform'\n | 'occluded'\n | 'point-budget'\n | 'query-budget'\n | 'redacted'\n | 'response-budget'\n | 'runtime-metadata-unavailable'\n | 'stale-mount'\n | 'unsupported-transform'\n | 'unsupported-boundary'\n | 'selection-unavailable'\n | 'selection-detached'\n | 'selection-excluded'\n | 'selection-redacted'\n\nexport interface AttentionOmission {\n reason: AttentionOmissionReason\n capture_code?: AttentionVisualCaptureOmissionCode\n point_id?: string\n mount_id?: string\n detail?: string\n}\n\nexport type AttentionVisualCaptureOmissionCode\n = | 'byte-limit'\n | 'capture-unavailable'\n | 'dimension-limit'\n | 'disabled'\n | 'encode-failed'\n | 'permission-denied'\n | 'pixel-limit'\n | 'privacy-excluded'\n | 'redaction-applied'\n | 'stale-target'\n | 'store-failed'\n | 'timeout'\n\nexport type AttentionCapabilityFeature\n = | 'observe-events'\n | 'query-points'\n | 'resolve-target'\n | 'highlight'\n | 'select'\n | 'capture-region'\n\nexport interface AttentionMountRef {\n mount_id: string\n generation: number\n}\n\nexport interface AttentionMountCoordinateSpace extends AttentionMountRef {\n kind: 'mount-viewport-css-px'\n}\n\nexport interface AttentionRuntimeBootstrapV1 {\n schema: typeof ATTENTION_RUNTIME_SCHEMA\n host_instance_id: string\n parent_origin: string\n target_origin: string\n parent_mount: AttentionMountRef\n target_mount: AttentionMountRef\n}\n\nexport interface AttentionLiveGeometry {\n coordinate_space: AttentionMountCoordinateSpace\n border_box: AttentionRect\n visible_clip?: AttentionRect\n local_to_parent?: AttentionTransform\n quality: 'exact' | 'approximate'\n}\n\nexport interface AttentionLiveQueryCandidateV1 {\n target_id: string\n path: AttentionPathSegment[]\n geometry: AttentionLiveGeometry\n sample_point_ids: string[]\n occluded: boolean\n summary: AttentionTargetSummary\n provenance?: AttentionCandidate['provenance']\n}\n\nexport interface AttentionCapabilityRequestV1 {\n schema: typeof ATTENTION_CAPABILITY_SCHEMA\n message_type: 'capability-request'\n request_id: string\n nonce: string\n host_instance_id: string\n parent_mount: AttentionMountRef\n target_mount: AttentionMountRef\n supported_versions: number[]\n}\n\nexport interface AttentionCapabilityGrantV1 {\n schema: typeof ATTENTION_CAPABILITY_SCHEMA\n message_type: 'capability-grant'\n request_id: string\n nonce: string\n host_instance_id: string\n parent_mount: AttentionMountRef\n target_mount: AttentionMountRef\n selected_version: 1 | 2\n capability_token: string\n issued_at: string\n expires_at: string\n features: AttentionCapabilityFeature[]\n coordinate_spaces: Array<'mount-viewport-css-px'>\n limits: {\n max_query_points: number\n max_candidates: number\n max_response_bytes: number\n }\n runtime?: {\n engine: 'iframe' | 'web-fragment' | 'web-component' | 'host'\n package_id?: string\n artifact_id?: string\n page_id?: string\n }\n}\n\nexport type AttentionCapabilityV1 = AttentionCapabilityRequestV1 | AttentionCapabilityGrantV1\n\nexport interface AttentionPointQueryV1 {\n schema: typeof ATTENTION_RELAY_SCHEMA\n message_type: 'query-request'\n request_id: string\n snapshot_id: string\n sequence: number\n host_instance_id: string\n parent_request_id?: string\n observation_scope?: 'focus'\n parent_mount: AttentionMountRef\n target_mount: AttentionMountRef\n capability_token: string\n created_at: string\n deadline_at: string\n coordinate_space: AttentionMountCoordinateSpace\n points: AttentionQueryPoint[]\n recursion: {\n depth: number\n max_depth: number\n }\n budget: AttentionQueryBudget\n}\n\nexport interface AttentionQueryBudget {\n remaining_depth: number\n remaining_queries: number\n remaining_query_points: number\n remaining_candidates: number\n remaining_bytes: number\n}\n\nexport type AttentionPointResultStatus = 'hit' | 'empty' | 'partial' | 'failed'\n\nexport interface AttentionPointResult {\n point_id: string\n status: AttentionPointResultStatus\n complete: boolean\n candidate_ids: string[]\n omissions: AttentionOmission[]\n}\n\nexport interface AttentionPointQueryResultV1 {\n schema: typeof ATTENTION_RELAY_SCHEMA\n message_type: 'query-result'\n request_id: string\n snapshot_id: string\n sequence: number\n host_instance_id: string\n source_mount: AttentionMountRef\n created_at: string\n point_results: AttentionPointResult[]\n candidates: AttentionLiveQueryCandidateV1[]\n observations?: AttentionObservationBatchV1\n omissions: AttentionOmission[]\n complete: boolean\n}\n\nexport type AttentionQueryErrorCode\n = | 'invalid-request'\n | 'unsupported-version'\n | 'unauthorized'\n | 'host-instance-mismatch'\n | 'capability-expired'\n | 'capability-revoked'\n | 'generation-mismatch'\n | 'deadline-exceeded'\n | 'detached'\n | 'navigation'\n | 'disconnected'\n | 'response-too-large'\n | 'internal-error'\n\nexport interface AttentionPointQueryErrorV1 {\n schema: typeof ATTENTION_RELAY_SCHEMA\n message_type: 'query-error'\n request_id: string\n snapshot_id: string\n sequence: number\n host_instance_id: string\n source_mount: AttentionMountRef\n created_at: string\n code: AttentionQueryErrorCode\n point_ids: string[]\n message?: string\n}\n\nexport interface AttentionSelectionUpdateV1 {\n schema: typeof ATTENTION_RELAY_SCHEMA\n message_type: 'selection-update'\n sequence: number\n slot_revision: number\n host_instance_id: string\n source_mount: AttentionMountRef\n parent_mount: AttentionMountRef\n capability_token: string\n observed_at: string\n selection_state: AttentionSelectionState\n}\n\nexport interface AttentionSelectionQueryRequestV1 {\n schema: typeof ATTENTION_RELAY_SCHEMA\n message_type: 'selection-query-request'\n request_id: string\n snapshot_id: string\n sequence: number\n host_instance_id: string\n parent_request_id?: string\n parent_mount: AttentionMountRef\n target_mount: AttentionMountRef\n capability_token: string\n created_at: string\n deadline_at: string\n remaining_depth: number\n remaining_queries: number\n remaining_bytes: number\n}\n\nexport interface AttentionSelectionQueryResultV1 {\n schema: typeof ATTENTION_RELAY_SCHEMA\n message_type: 'selection-query-result'\n request_id: string\n snapshot_id: string\n sequence: number\n host_instance_id: string\n source_mount: AttentionMountRef\n created_at: string\n slot_revision: number\n selection_state: AttentionSelectionState\n complete: boolean\n omissions: AttentionOmission[]\n}\n\nexport interface AttentionSelectionQueryErrorV1 {\n schema: typeof ATTENTION_RELAY_SCHEMA\n message_type: 'selection-query-error'\n request_id: string\n snapshot_id: string\n sequence: number\n host_instance_id: string\n source_mount: AttentionMountRef\n created_at: string\n code: AttentionQueryErrorCode\n message?: string\n}\n\nexport type AttentionRelayMessageV1 = AttentionPointQueryV1 | AttentionPointQueryResultV1 | AttentionPointQueryErrorV1 | AttentionSelectionUpdateV1 | AttentionSelectionQueryRequestV1 | AttentionSelectionQueryResultV1 | AttentionSelectionQueryErrorV1\n\nexport interface AttentionTargetRefV1 {\n snapshot_id: string\n target_id: string\n host_instance_id: string\n mount_id: string\n generation: number\n path_digest: `sha256:${string}`\n rect: AttentionRect\n label?: string\n}\n\nexport type AttentionUiActionMode = 'highlight' | 'confirm' | 'select' | 'capture_visual'\n\nexport type AttentionVisualCaptureScope = 'target' | 'region' | 'viewport'\n\nexport interface AttentionUiActionCaptureV1 {\n scope: AttentionVisualCaptureScope\n region?: AttentionRect\n allow_adjustment: boolean\n allow_viewport_choice: boolean\n format: 'image/png' | 'image/webp'\n}\n\nexport interface AttentionUiActionRequestV1 {\n schema: typeof ATTENTION_UI_ACTION_SCHEMA\n message_type: 'request'\n action_id: string\n request_id: string\n session_id: string\n host_instance_id: string\n created_at: string\n expires_at: string\n mode: AttentionUiActionMode\n prompt?: string\n targets: AttentionTargetRefV1[]\n allow_pointer: boolean\n allow_keyboard: boolean\n capture_region?: boolean\n capture?: AttentionUiActionCaptureV1\n}\n\nexport type AttentionUiActionStatus\n = | 'selected'\n | 'confirmed'\n | 'prepared'\n | 'cancelled'\n | 'denied'\n | 'rejected'\n | 'expired'\n | 'stale'\n | 'disconnected'\n | 'permission-denied'\n | 'unavailable'\n | 'error'\n\nexport interface AttentionPreparedFileV1 {\n uuid: string\n name: string\n mime_type: 'image/png' | 'image/webp'\n byte_size: number\n sha256: `sha256:${string}`\n scope: AttentionVisualCaptureScope\n}\n\nexport interface AttentionUiActionResultV1 {\n schema: typeof ATTENTION_UI_ACTION_SCHEMA\n message_type: 'result'\n result_id: string\n in_reply_to_action_id: string\n request_id: string\n session_id: string\n host_instance_id: string\n completed_at: string\n status: AttentionUiActionStatus\n selected_target?: AttentionTargetRefV1\n prepared_file?: AttentionPreparedFileV1\n reason?: string\n}\n\nexport type AttentionUiActionMessageV1 = AttentionUiActionRequestV1 | AttentionUiActionResultV1\n\nexport interface AttentionVisualContextV1 {\n schema: typeof ATTENTION_VISUAL_CONTEXT_SCHEMA\n capture_id: string\n snapshot_id: string\n host_instance_id: string\n created_at: string\n expires_at: string\n candidate_ids: string[]\n region: AttentionRect\n media: {\n content_type: 'image/png' | 'image/webp'\n content_bytes: number\n content_hash: `sha256:${string}`\n pixel_width: number\n pixel_height: number\n }\n reference: {\n kind: 'upload'\n opaque_id: string\n }\n authorization: {\n scope: 'session'\n session_id: string\n audience: 'agent-context'\n expires_at: string\n }\n redactions_applied: number\n}\n\nexport interface AttentionSnapshotV1 {\n schema: typeof ATTENTION_CONTEXT_SCHEMA\n snapshot_id: string\n host_instance_id: string\n mount_generation: number\n created_at: string\n coordinate_space: AttentionCoordinateSpace\n capture: {\n radius_css_px: number\n grid_step_css_px: number\n sampled_points: number\n points: AttentionQueryPoint[]\n duration_ms: number\n complete: boolean\n }\n selection?: AttentionSelection\n pointer?: AttentionObservedEvent\n focus?: AttentionFocus\n recent_events: AttentionObservedEvent[]\n candidates: AttentionCandidate[]\n omissions: AttentionOmission[]\n}\n\nexport type AttentionContextAttachment = ContextAttachmentEnvelope\n\nexport type AttentionSampleRefV2 = string | number | [number, number]\nexport interface AttentionGridEncodingV2 {\n kind: 'css-euclidean-grid.v1'\n origin: AttentionPoint\n overrides?: { index: number, point: AttentionQueryPoint }[]\n additional_points?: AttentionQueryPoint[]\n}\nexport interface AttentionCandidateV2 extends Omit<AttentionCandidate, 'path' | 'sample_point_ids'> {\n path_indices: number[]\n sample_refs: AttentionSampleRefV2[]\n}\nexport interface AttentionSnapshotV2 extends Omit<AttentionSnapshotV1, 'schema' | 'capture' | 'candidates' | 'focus'> {\n schema: typeof ATTENTION_CONTEXT_V2_SCHEMA\n path_dictionary: AttentionPathSegment[]\n candidates: AttentionCandidateV2[]\n focus?: Omit<AttentionFocus, 'path'> & { path_indices: number[] }\n capture: Omit<AttentionSnapshotV1['capture'], 'points'> & (\n { points: AttentionQueryPoint[], point_encoding?: never }\n | { point_encoding: AttentionGridEncodingV2, points?: never }\n )\n}\n\nexport type AttentionRectTupleV3 = [number, number, number, number]\nexport interface AttentionPathAttributesV3 extends Omit<AttentionPathSegment, 'kind' | 'mount_id' | 'generation' | 'rect' | 'clip_rect' | 'local_to_parent'> {\n rect?: AttentionRectTupleV3\n clip_rect?: AttentionRectTupleV3\n local_to_parent?: AttentionTransform['matrix']\n}\nexport type AttentionPathSegmentV3 = [AttentionRealmKind, string, number, AttentionPathAttributesV3]\nexport interface AttentionSnapshotV3 extends Omit<AttentionSnapshotV2, 'schema' | 'path_dictionary'> {\n schema: typeof ATTENTION_CONTEXT_V3_SCHEMA\n path_dictionary: AttentionPathSegmentV3[]\n}\n\nexport interface AttentionSelectionV4 extends Omit<AttentionSelection, 'anchor_path' | 'focus_path'> {\n anchor_path_indices: number[]\n focus_path_indices: number[]\n}\n\nexport interface AttentionSnapshotV4 extends Omit<AttentionSnapshotV3, 'schema' | 'selection'> {\n schema: typeof ATTENTION_CONTEXT_V4_SCHEMA\n selection?: AttentionSelectionV4\n}\n\nexport interface ContextAttachmentValidationOptions {\n now?: number\n allowExpired?: boolean\n maxCount?: number\n maxTotalBytes?: number\n maxAttachmentBytes?: number\n}\n\nexport interface ContextAttachmentValidationError {\n attachment_id?: string\n code:\n | 'array-required'\n | 'too-many-attachments'\n | 'total-bytes-exceeded'\n | 'invalid-envelope'\n | 'duplicate-attachment-id'\n | 'attachment-bytes-exceeded'\n | 'content-size-mismatch'\n | 'invalid-content-hash'\n | 'content-hash-mismatch'\n | 'action-ref-path-digest-mismatch'\n | 'noncanonical-content'\n | 'forbidden-live-field'\n | 'expired'\n | 'invalid-attention-payload'\n path: string\n message: string\n}\n\nexport type ContextAttachmentValidationResult\n = | { ok: true, attachments: ContextAttachmentEnvelope[] }\n | { ok: false, errors: ContextAttachmentValidationError[] }\n\nfunction hasOwn(value: object, key: PropertyKey): boolean {\n // eslint-disable-next-line e18e/prefer-object-has-own -- The shared package targets ES2020.\n return Object.prototype.hasOwnProperty.call(value, key)\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object' || Array.isArray(value))\n return false\n const prototype = Object.getPrototypeOf(value)\n if (prototype === null)\n return true\n const constructor = hasOwn(prototype, 'constructor')\n ? prototype.constructor\n : undefined\n return typeof constructor === 'function'\n && Function.prototype.toString.call(constructor) === Function.prototype.toString.call(Object)\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value)\n}\n\nfunction isNonEmptyString(value: unknown, maxLength: number): value is string {\n return typeof value === 'string' && value.length > 0 && value.length <= maxLength\n}\n\nfunction isOptionalString(value: unknown, maxLength: number): value is string | undefined {\n return value === undefined || (typeof value === 'string' && value.length <= maxLength)\n}\n\nfunction isIsoTimestamp(value: unknown): value is string {\n if (!isNonEmptyString(value, 64))\n return false\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.\\d{1,9})?Z$/.exec(value)\n if (!match)\n return false\n const [, yearText, monthText, dayText, hourText, minuteText, secondText] = match\n const year = Number(yearText)\n const month = Number(monthText)\n const day = Number(dayText)\n const hour = Number(hourText)\n const minute = Number(minuteText)\n const second = Number(secondText)\n const leapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)\n const daysInMonth = [31, leapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]\n return year >= 1\n && month >= 1 && month <= 12\n && day >= 1 && day <= daysInMonth[month - 1]\n && hour <= 23\n && minute <= 59\n && second <= 59\n && Number.isFinite(Date.parse(value))\n}\n\nfunction isFrameOrigin(value: unknown): value is string | undefined {\n if (value === undefined)\n return true\n if (value === 'about:srcdoc')\n return true\n if (typeof value !== 'string' || value.length > 2048)\n return false\n try {\n const parsed = new URL(value)\n return (parsed.protocol === 'http:' || parsed.protocol === 'https:')\n && parsed.origin === value\n && parsed.username === ''\n && parsed.password === ''\n && parsed.pathname === '/'\n && parsed.search === ''\n && parsed.hash === ''\n }\n catch {\n return false\n }\n}\n\nfunction isWebOrigin(value: unknown): value is string {\n return typeof value === 'string'\n && (value.startsWith('https://') || value.startsWith('http://'))\n && isFrameOrigin(value)\n}\n\nfunction isRect(value: unknown): value is AttentionRect {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['x', 'y', 'width', 'height'])\n && isFiniteNumber(value.x)\n && isFiniteNumber(value.y)\n && isFiniteNumber(value.width)\n && value.width >= 0\n && isFiniteNumber(value.height)\n && value.height >= 0\n}\n\nfunction isPoint(value: unknown): value is AttentionPoint {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['x', 'y'])\n && isFiniteNumber(value.x)\n && isFiniteNumber(value.y)\n}\n\nfunction isQueryPoint(value: unknown): value is AttentionQueryPoint {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['point_id', 'x', 'y'])\n && isNonEmptyString(value.point_id, 128)\n && isFiniteNumber(value.x)\n && isFiniteNumber(value.y)\n}\n\nfunction isPathSegment(value: unknown): value is AttentionPathSegment {\n if (!isPlainObject(value))\n return false\n const kinds: AttentionRealmKind[] = ['host', 'panel', 'artifact', 'page', 'iframe', 'web-fragment', 'web-component', 'shadow-root', 'element']\n return hasOnlyKeys(value, ['kind', 'mount_id', 'generation', 'label', 'panel_id', 'surface_id', 'artifact_id', 'page_id', 'package_id', 'tag_name', 'selector_hint', 'frame_origin', 'rect', 'clip_rect', 'local_to_parent', 'coordinate_quality'])\n && kinds.includes(value.kind as AttentionRealmKind)\n && isNonEmptyString(value.mount_id, 160)\n && Number.isSafeInteger(value.generation)\n && (value.generation as number) >= 0\n && isOptionalString(value.label, 256)\n && isOptionalString(value.panel_id, 128)\n && isOptionalString(value.surface_id, 128)\n && isOptionalString(value.artifact_id, 128)\n && isOptionalString(value.page_id, 128)\n && isOptionalString(value.package_id, 256)\n && isOptionalString(value.tag_name, 128)\n && isOptionalString(value.selector_hint, 512)\n && isFrameOrigin(value.frame_origin)\n && (value.rect === undefined || isRect(value.rect))\n && (value.clip_rect === undefined || isRect(value.clip_rect))\n && (value.local_to_parent === undefined || isTransform(value.local_to_parent))\n && (value.coordinate_quality === undefined || value.coordinate_quality === 'exact' || value.coordinate_quality === 'approximate')\n}\n\nexport function isAttentionStructuralPath(value: unknown): value is AttentionPathSegment[] {\n return Array.isArray(value) && value.length > 0 && value.length <= 32 && value.every(isPathSegment)\n}\n\nconst isPath = isAttentionStructuralPath\n\nfunction isSummary(value: unknown): value is AttentionTargetSummary {\n if (!isPlainObject(value))\n return false\n if (!hasOnlyKeys(value, ['role', 'name', 'text', 'value', 'state']))\n return false\n const stringsValid = ['role', 'name', 'text', 'value'].every((key) => {\n const member = value[key]\n return isOptionalString(member, key === 'text' ? 1024 : 256)\n })\n if (!stringsValid || (value.state !== undefined && !isPlainObject(value.state)))\n return false\n return value.state === undefined || Object.values(value.state).every(member => member === null\n || typeof member === 'boolean'\n || typeof member === 'string'\n || isFiniteNumber(member))\n}\n\nexport function isAttentionObservedEvent(value: unknown): value is AttentionObservedEvent {\n if (!isPlainObject(value))\n return false\n const types = ['pointermove', 'pointerdown', 'pointerup', 'click', 'touchstart', 'touchend']\n const pointerTypes = ['mouse', 'pen', 'touch', 'unknown']\n return hasOnlyKeys(value, ['event_id', 'sequence', 'type', 'observed_at', 'realm_time_ms', 'point', 'pointer_id', 'pointer_type', 'buttons', 'pointer_capture', 'candidate_ids'])\n && isNonEmptyString(value.event_id, 128)\n && Number.isSafeInteger(value.sequence) && (value.sequence as number) >= 0\n && types.includes(value.type as string)\n && isIsoTimestamp(value.observed_at)\n && isFiniteNumber(value.realm_time_ms) && (value.realm_time_ms as number) >= 0\n && isPoint(value.point)\n && (value.pointer_id === undefined || Number.isSafeInteger(value.pointer_id))\n && (value.pointer_type === undefined || pointerTypes.includes(value.pointer_type as string))\n && (value.buttons === undefined || (Number.isSafeInteger(value.buttons) && (value.buttons as number) >= 0))\n && (value.pointer_capture === undefined || typeof value.pointer_capture === 'boolean')\n && Array.isArray(value.candidate_ids)\n && value.candidate_ids.length <= 128\n && new Set(value.candidate_ids).size === value.candidate_ids.length\n && value.candidate_ids.every(id => isNonEmptyString(id, 128))\n}\n\nconst isObservedEvent = isAttentionObservedEvent\n\nexport function isAttentionFocus(value: unknown): value is AttentionFocus {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['event_id', 'sequence', 'focused_at', 'realm_time_ms', 'candidate_id', 'path', 'summary'])\n && isNonEmptyString(value.event_id, 128)\n && Number.isSafeInteger(value.sequence) && (value.sequence as number) >= 0\n && isIsoTimestamp(value.focused_at)\n && isFiniteNumber(value.realm_time_ms) && (value.realm_time_ms as number) >= 0\n && isOptionalString(value.candidate_id, 128)\n && isPath(value.path)\n && isSummary(value.summary)\n}\n\nexport function isAttentionFocusState(value: unknown): value is AttentionFocusState {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['changed_at', 'focus', 'retained'])\n && isIsoTimestamp(value.changed_at)\n && (value.focus === null || isAttentionFocus(value.focus))\n && typeof value.retained === 'boolean'\n && (value.focus !== null || value.retained === false)\n}\n\nexport function isAttentionSelection(value: unknown): value is AttentionSelection {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['selection_id', 'selected_at', 'kind', 'collapsed', 'direction', 'text', 'anchor_path', 'focus_path', 'ranges'])\n && isNonEmptyString(value.selection_id, 128)\n && isIsoTimestamp(value.selected_at)\n && value.kind === 'text'\n && value.collapsed === false\n && ['forward', 'backward', 'none'].includes(value.direction as string)\n && typeof value.text === 'string'\n && utf8ByteLength(value.text) <= 1024\n && isPath(value.anchor_path)\n && isPath(value.focus_path)\n && Array.isArray(value.ranges)\n && value.ranges.length <= 4\n && value.ranges.every(range => isPlainObject(range)\n && hasOnlyKeys(range, ['rect', 'coordinate_space'])\n && isRect(range.rect)\n && (range.coordinate_space === 'host-viewport' || isMountRef(range.coordinate_space)))\n}\n\nfunction isObservationBatch(value: unknown): value is AttentionObservationBatchV1 {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['received_at', 'pointer', 'focus', 'focus_state', 'selection', 'recent_events'])\n && isIsoTimestamp(value.received_at)\n && (value.pointer === undefined || isObservedEvent(value.pointer))\n && (value.focus === undefined || isAttentionFocus(value.focus))\n && (value.focus_state === undefined || isAttentionFocusState(value.focus_state))\n && (value.selection === undefined || isAttentionSelection(value.selection))\n && Array.isArray(value.recent_events)\n && value.recent_events.length <= 32\n && value.recent_events.every(isObservedEvent)\n && new Set(value.recent_events.map(event => event.event_id)).size === value.recent_events.length\n}\n\nfunction hasOnlyKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {\n const allowed = new Set(keys)\n return Object.keys(value).every(key => allowed.has(key))\n}\n\nfunction isMountRef(value: unknown): value is AttentionMountRef {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['mount_id', 'generation'])\n && isNonEmptyString(value.mount_id, 160)\n && Number.isSafeInteger(value.generation)\n && (value.generation as number) >= 0\n}\n\nfunction hasMountValues(value: Record<string, unknown>): boolean {\n return isNonEmptyString(value.mount_id, 160)\n && Number.isSafeInteger(value.generation)\n && (value.generation as number) >= 0\n}\n\nfunction sameMount(left: AttentionMountRef, right: AttentionMountRef): boolean {\n return left.mount_id === right.mount_id && left.generation === right.generation\n}\n\nfunction isQueryBudget(value: unknown): value is AttentionQueryBudget {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['remaining_depth', 'remaining_queries', 'remaining_query_points', 'remaining_candidates', 'remaining_bytes'])\n && Number.isSafeInteger(value.remaining_depth) && (value.remaining_depth as number) >= 0 && (value.remaining_depth as number) <= 16\n && Number.isSafeInteger(value.remaining_queries) && (value.remaining_queries as number) >= 0 && (value.remaining_queries as number) <= 128\n && Number.isSafeInteger(value.remaining_query_points) && (value.remaining_query_points as number) >= 0 && (value.remaining_query_points as number) <= 4096\n && Number.isSafeInteger(value.remaining_candidates) && (value.remaining_candidates as number) >= 0 && (value.remaining_candidates as number) <= ATTENTION_MAX_CANDIDATES\n && Number.isSafeInteger(value.remaining_bytes) && (value.remaining_bytes as number) >= 0 && (value.remaining_bytes as number) <= 1_048_576\n}\n\nfunction isOmission(value: unknown): value is AttentionOmission {\n if (!isPlainObject(value))\n return false\n const reasons: AttentionOmissionReason[] = [\n 'candidate-budget',\n 'capability-unavailable',\n 'child-disconnected',\n 'child-timeout',\n 'clipped',\n 'depth-limit',\n 'excluded',\n 'navigation',\n 'non-invertible-transform',\n 'occluded',\n 'point-budget',\n 'query-budget',\n 'redacted',\n 'response-budget',\n 'runtime-metadata-unavailable',\n 'stale-mount',\n 'unsupported-transform',\n 'unsupported-boundary',\n 'selection-unavailable',\n 'selection-detached',\n 'selection-excluded',\n 'selection-redacted',\n ]\n const captureCodes: AttentionVisualCaptureOmissionCode[] = [\n 'byte-limit',\n 'capture-unavailable',\n 'dimension-limit',\n 'disabled',\n 'encode-failed',\n 'permission-denied',\n 'pixel-limit',\n 'privacy-excluded',\n 'redaction-applied',\n 'stale-target',\n 'store-failed',\n 'timeout',\n ]\n return hasOnlyKeys(value, ['reason', 'capture_code', 'point_id', 'mount_id', 'detail'])\n && reasons.includes(value.reason as AttentionOmissionReason)\n && (value.capture_code === undefined || captureCodes.includes(value.capture_code as AttentionVisualCaptureOmissionCode))\n && (value.point_id === undefined || isNonEmptyString(value.point_id, 128))\n && (value.mount_id === undefined || isNonEmptyString(value.mount_id, 160))\n && (value.detail === undefined || isNonEmptyString(value.detail, 512))\n}\n\nfunction isPointResult(value: unknown): value is AttentionPointResult {\n if (!isPlainObject(value))\n return false\n const statuses: AttentionPointResultStatus[] = ['hit', 'empty', 'partial', 'failed']\n const shapeValid = hasOnlyKeys(value, ['point_id', 'status', 'complete', 'candidate_ids', 'omissions'])\n && isNonEmptyString(value.point_id, 128)\n && statuses.includes(value.status as AttentionPointResultStatus)\n && typeof value.complete === 'boolean'\n && Array.isArray(value.candidate_ids) && value.candidate_ids.length <= 128\n && new Set(value.candidate_ids).size === value.candidate_ids.length\n && value.candidate_ids.every(id => isNonEmptyString(id, 160))\n && Array.isArray(value.omissions) && value.omissions.length <= 32 && value.omissions.every(isOmission)\n if (!shapeValid)\n return false\n const pointResult = value as unknown as AttentionPointResult\n const partial = pointResult.omissions.length > 0\n return pointResult.complete !== partial\n && (!pointResult.complete || pointResult.status !== 'partial')\n && (pointResult.complete || pointResult.status === 'partial' || pointResult.status === 'failed')\n && (pointResult.status !== 'hit' || pointResult.candidate_ids.length > 0)\n && (pointResult.status !== 'empty' || pointResult.candidate_ids.length === 0)\n}\n\nfunction isTransform(value: unknown): value is AttentionTransform {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['matrix', 'convention', 'direction'])\n && Array.isArray(value.matrix) && value.matrix.length === 16 && value.matrix.every(isFiniteNumber)\n && value.convention === 'dommatrix-column-major'\n && value.direction === 'local-to-parent'\n}\n\nfunction isProvenance(value: unknown): value is NonNullable<AttentionCandidate['provenance']> {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['geometry_source', 'runtime_source'])\n && (value.geometry_source === 'document' || value.geometry_source === 'physical-host')\n && (value.runtime_source === 'document' || value.runtime_source === 'iframe-realm' || value.runtime_source === 'fragment-realm')\n}\n\nfunction isLiveCandidate(value: unknown): value is AttentionLiveQueryCandidateV1 {\n if (!isPlainObject(value) || !isPlainObject(value.geometry))\n return false\n const geometry = value.geometry\n return hasOnlyKeys(value, ['target_id', 'path', 'geometry', 'sample_point_ids', 'occluded', 'summary', 'provenance'])\n && isNonEmptyString(value.target_id, 160)\n && isPath(value.path)\n && hasOnlyKeys(geometry, ['coordinate_space', 'border_box', 'visible_clip', 'local_to_parent', 'quality'])\n && isPlainObject(geometry.coordinate_space)\n && geometry.coordinate_space.kind === 'mount-viewport-css-px'\n && hasOnlyKeys(geometry.coordinate_space, ['kind', 'mount_id', 'generation'])\n && hasMountValues(geometry.coordinate_space)\n && isRect(geometry.border_box)\n && (geometry.visible_clip === undefined || isRect(geometry.visible_clip))\n && (geometry.local_to_parent === undefined || isTransform(geometry.local_to_parent))\n && (geometry.quality === 'exact' || geometry.quality === 'approximate')\n && Array.isArray(value.sample_point_ids) && value.sample_point_ids.length >= 1 && value.sample_point_ids.length <= 4096\n && new Set(value.sample_point_ids).size === value.sample_point_ids.length\n && value.sample_point_ids.every(id => isNonEmptyString(id, 128))\n && typeof value.occluded === 'boolean'\n && isSummary(value.summary)\n && (value.provenance === undefined || isProvenance(value.provenance))\n}\n\nexport function isAttentionRuntimeBootstrapV1(value: unknown): value is AttentionRuntimeBootstrapV1 {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['schema', 'host_instance_id', 'parent_origin', 'target_origin', 'parent_mount', 'target_mount'])\n && value.schema === ATTENTION_RUNTIME_SCHEMA\n && isNonEmptyString(value.host_instance_id, 160)\n && isWebOrigin(value.parent_origin)\n && isWebOrigin(value.target_origin)\n && isMountRef(value.parent_mount)\n && isMountRef(value.target_mount)\n}\n\nexport function isAttentionCapabilityV1(value: unknown): value is AttentionCapabilityV1 {\n if (!isPlainObject(value) || value.schema !== ATTENTION_CAPABILITY_SCHEMA)\n return false\n const common = isNonEmptyString(value.request_id, 160)\n && isNonEmptyString(value.nonce, 512) && value.nonce.length >= 32\n && isNonEmptyString(value.host_instance_id, 160)\n && isMountRef(value.parent_mount)\n && isMountRef(value.target_mount)\n if (!common)\n return false\n if (value.message_type === 'capability-request') {\n return hasOnlyKeys(value, ['schema', 'message_type', 'request_id', 'nonce', 'host_instance_id', 'parent_mount', 'target_mount', 'supported_versions'])\n && Array.isArray(value.supported_versions) && value.supported_versions.length >= 1 && value.supported_versions.length <= 16\n && new Set(value.supported_versions).size === value.supported_versions.length\n && value.supported_versions.every(version => Number.isSafeInteger(version) && (version as number) >= 1)\n }\n if (value.message_type !== 'capability-grant')\n return false\n const features: AttentionCapabilityFeature[] = ['observe-events', 'query-points', 'resolve-target', 'highlight', 'select', 'capture-region']\n const runtimeEngines = ['iframe', 'web-fragment', 'web-component', 'host']\n const runtimeValid = value.runtime === undefined || (isPlainObject(value.runtime)\n && hasOnlyKeys(value.runtime, ['engine', 'package_id', 'artifact_id', 'page_id'])\n && runtimeEngines.includes(value.runtime.engine as string)\n && isOptionalString(value.runtime.package_id, 256)\n && isOptionalString(value.runtime.artifact_id, 128)\n && isOptionalString(value.runtime.page_id, 128))\n return hasOnlyKeys(value, ['schema', 'message_type', 'request_id', 'nonce', 'host_instance_id', 'parent_mount', 'target_mount', 'selected_version', 'capability_token', 'issued_at', 'expires_at', 'features', 'coordinate_spaces', 'limits', 'runtime'])\n && (value.selected_version === 1 || value.selected_version === 2)\n && isNonEmptyString(value.capability_token, 512) && value.capability_token.length >= 32\n && isIsoTimestamp(value.issued_at) && isIsoTimestamp(value.expires_at)\n && Array.isArray(value.features) && new Set(value.features).size === value.features.length && value.features.every(feature => features.includes(feature as AttentionCapabilityFeature))\n && Array.isArray(value.coordinate_spaces) && value.coordinate_spaces.length >= 1 && new Set(value.coordinate_spaces).size === value.coordinate_spaces.length && value.coordinate_spaces.every(space => space === 'mount-viewport-css-px')\n && isPlainObject(value.limits)\n && hasOnlyKeys(value.limits, ['max_query_points', 'max_candidates', 'max_response_bytes'])\n && Number.isSafeInteger(value.limits.max_query_points) && (value.limits.max_query_points as number) >= 1 && (value.limits.max_query_points as number) <= 4096\n && Number.isSafeInteger(value.limits.max_candidates) && (value.limits.max_candidates as number) >= 1 && (value.limits.max_candidates as number) <= 128\n && Number.isSafeInteger(value.limits.max_response_bytes) && (value.limits.max_response_bytes as number) >= 1024 && (value.limits.max_response_bytes as number) <= 1_048_576\n && runtimeValid\n}\n\nexport function isAttentionSelectionState(state: unknown): state is AttentionSelectionState {\n if (!isPlainObject(state) || typeof state.state !== 'string')\n return false\n if (state.state === 'unknown')\n return hasOnlyKeys(state, ['state', 'reason']) && ['unavailable', 'privacy', 'detached', 'unsupported'].includes(state.reason as string)\n if (state.state === 'selected') {\n return hasOnlyKeys(state, ['state', 'origin_sequence', 'origin_mount', 'selection'])\n && Number.isSafeInteger(state.origin_sequence) && (state.origin_sequence as number) >= 0\n && isMountRef(state.origin_mount)\n && isAttentionSelection(state.selection)\n }\n return state.state === 'cleared'\n && hasOnlyKeys(state, ['state', 'origin_sequence', 'origin_mount', 'reason', 'cleared_at'])\n && Number.isSafeInteger(state.origin_sequence) && (state.origin_sequence as number) >= 0\n && isMountRef(state.origin_mount)\n && (state.cleared_at === undefined || isIsoTimestamp(state.cleared_at))\n && ['user-cleared', 'detached', 'navigation', 'privacy', 'disconnected', 'revoked'].includes(state.reason as string)\n}\n\nexport function isAttentionRelayMessageV1(value: unknown): value is AttentionRelayMessageV1 {\n if (!isPlainObject(value) || value.schema !== ATTENTION_RELAY_SCHEMA)\n return false\n const selectionStateValid = isAttentionSelectionState\n if (value.message_type === 'selection-update') {\n return hasOnlyKeys(value, ['schema', 'message_type', 'sequence', 'slot_revision', 'host_instance_id', 'source_mount', 'parent_mount', 'capability_token', 'observed_at', 'selection_state'])\n && Number.isSafeInteger(value.sequence) && (value.sequence as number) >= 0\n && Number.isSafeInteger(value.slot_revision) && (value.slot_revision as number) >= 0\n && isNonEmptyString(value.host_instance_id, 160)\n && isMountRef(value.source_mount) && isMountRef(value.parent_mount)\n && isNonEmptyString(value.capability_token, 512) && value.capability_token.length >= 32\n && isIsoTimestamp(value.observed_at)\n && selectionStateValid(value.selection_state)\n }\n if (value.message_type === 'selection-query-request') {\n return hasOnlyKeys(value, ['schema', 'message_type', 'request_id', 'snapshot_id', 'sequence', 'host_instance_id', 'parent_request_id', 'parent_mount', 'target_mount', 'capability_token', 'created_at', 'deadline_at', 'remaining_depth', 'remaining_queries', 'remaining_bytes'])\n && (value.parent_request_id === undefined || isNonEmptyString(value.parent_request_id, 160))\n && isNonEmptyString(value.request_id, 160) && isNonEmptyString(value.snapshot_id, 160)\n && Number.isSafeInteger(value.sequence) && (value.sequence as number) >= 0\n && isNonEmptyString(value.host_instance_id, 160)\n && isMountRef(value.parent_mount) && isMountRef(value.target_mount)\n && isNonEmptyString(value.capability_token, 512) && value.capability_token.length >= 32\n && isIsoTimestamp(value.created_at) && isIsoTimestamp(value.deadline_at)\n && Number.isSafeInteger(value.remaining_depth) && (value.remaining_depth as number) >= 0 && (value.remaining_depth as number) <= 16\n && Number.isSafeInteger(value.remaining_queries) && (value.remaining_queries as number) >= 0 && (value.remaining_queries as number) <= 128\n && Number.isSafeInteger(value.remaining_bytes) && (value.remaining_bytes as number) >= 0 && (value.remaining_bytes as number) <= 1_048_576\n }\n if (value.message_type === 'selection-query-result') {\n return hasOnlyKeys(value, ['schema', 'message_type', 'request_id', 'snapshot_id', 'sequence', 'slot_revision', 'host_instance_id', 'source_mount', 'created_at', 'selection_state', 'complete', 'omissions'])\n && isNonEmptyString(value.request_id, 160) && isNonEmptyString(value.snapshot_id, 160)\n && Number.isSafeInteger(value.sequence) && (value.sequence as number) >= 0\n && Number.isSafeInteger(value.slot_revision) && (value.slot_revision as number) >= 0\n && isNonEmptyString(value.host_instance_id, 160) && isMountRef(value.source_mount)\n && isIsoTimestamp(value.created_at) && selectionStateValid(value.selection_state)\n && typeof value.complete === 'boolean'\n && Array.isArray(value.omissions) && value.omissions.length <= 128 && value.omissions.every(isOmission)\n }\n if (value.message_type === 'selection-query-error') {\n return hasOnlyKeys(value, ['schema', 'message_type', 'request_id', 'snapshot_id', 'sequence', 'host_instance_id', 'source_mount', 'created_at', 'code', 'message'])\n && isNonEmptyString(value.request_id, 160) && isNonEmptyString(value.snapshot_id, 160)\n && Number.isSafeInteger(value.sequence) && (value.sequence as number) >= 0\n && isNonEmptyString(value.host_instance_id, 160) && isMountRef(value.source_mount)\n && isIsoTimestamp(value.created_at)\n && ['invalid-request', 'unsupported-version', 'unauthorized', 'host-instance-mismatch', 'capability-expired', 'capability-revoked', 'generation-mismatch', 'deadline-exceeded', 'detached', 'navigation', 'disconnected', 'response-too-large', 'internal-error'].includes(value.code as string)\n && (value.message === undefined || isNonEmptyString(value.message, 512))\n }\n const common = isNonEmptyString(value.request_id, 160)\n && isNonEmptyString(value.snapshot_id, 160)\n && Number.isSafeInteger(value.sequence) && (value.sequence as number) >= 0\n && isNonEmptyString(value.host_instance_id, 160)\n && isIsoTimestamp(value.created_at)\n if (!common)\n return false\n if (value.message_type === 'query-request') {\n return hasOnlyKeys(value, ['schema', 'message_type', 'request_id', 'snapshot_id', 'sequence', 'host_instance_id', 'parent_request_id', 'observation_scope', 'parent_mount', 'target_mount', 'capability_token', 'created_at', 'deadline_at', 'coordinate_space', 'points', 'recursion', 'budget'])\n && (value.observation_scope === undefined || (value.observation_scope === 'focus' && Array.isArray(value.points) && value.points.length === 0))\n && (value.parent_request_id === undefined || isNonEmptyString(value.parent_request_id, 160))\n && isMountRef(value.parent_mount) && isMountRef(value.target_mount)\n && isNonEmptyString(value.capability_token, 512) && value.capability_token.length >= 32\n && isIsoTimestamp(value.deadline_at)\n && isPlainObject(value.coordinate_space) && value.coordinate_space.kind === 'mount-viewport-css-px'\n && hasOnlyKeys(value.coordinate_space, ['kind', 'mount_id', 'generation']) && hasMountValues(value.coordinate_space)\n && sameMount(value.coordinate_space as unknown as AttentionMountRef, value.target_mount as unknown as AttentionMountRef)\n && Array.isArray(value.points) && value.points.length <= 4096 && value.points.every(isQueryPoint)\n && new Set(value.points.map((point: AttentionQueryPoint) => point.point_id)).size === value.points.length\n && isPlainObject(value.recursion)\n && hasOnlyKeys(value.recursion, ['depth', 'max_depth'])\n && Number.isSafeInteger(value.recursion.depth) && (value.recursion.depth as number) >= 0 && (value.recursion.depth as number) <= 16\n && Number.isSafeInteger(value.recursion.max_depth) && (value.recursion.max_depth as number) >= 0 && (value.recursion.max_depth as number) <= 16\n && (value.recursion.depth as number) <= (value.recursion.max_depth as number)\n && isQueryBudget(value.budget)\n && (value.budget as AttentionQueryBudget).remaining_depth <= (value.recursion.max_depth as number) - (value.recursion.depth as number)\n }\n if (value.message_type === 'query-result') {\n const shapeValid = hasOnlyKeys(value, ['schema', 'message_type', 'request_id', 'snapshot_id', 'sequence', 'host_instance_id', 'source_mount', 'created_at', 'point_results', 'candidates', 'observations', 'omissions', 'complete'])\n && isMountRef(value.source_mount)\n && Array.isArray(value.point_results) && value.point_results.length <= 4096 && value.point_results.every(isPointResult)\n && Array.isArray(value.candidates) && value.candidates.length <= 128 && value.candidates.every(isLiveCandidate)\n && (value.observations === undefined || isObservationBatch(value.observations))\n && Array.isArray(value.omissions) && value.omissions.length <= 128 && value.omissions.every(isOmission)\n && typeof value.complete === 'boolean'\n if (!shapeValid)\n return false\n const queryResult = value as unknown as AttentionPointQueryResultV1\n const candidateIds = new Set(queryResult.candidates.map(candidate => candidate.target_id))\n return candidateIds.size === queryResult.candidates.length\n && new Set(queryResult.point_results.map(result => result.point_id)).size === queryResult.point_results.length\n && queryResult.point_results.every(result => result.candidate_ids.every(id => candidateIds.has(id)))\n && queryResult.complete === (queryResult.omissions.length === 0 && queryResult.point_results.every(result => result.complete))\n }\n if (value.message_type !== 'query-error')\n return false\n const codes: AttentionQueryErrorCode[] = ['invalid-request', 'unsupported-version', 'unauthorized', 'host-instance-mismatch', 'capability-expired', 'capability-revoked', 'generation-mismatch', 'deadline-exceeded', 'detached', 'navigation', 'disconnected', 'response-too-large', 'internal-error']\n return hasOnlyKeys(value, ['schema', 'message_type', 'request_id', 'snapshot_id', 'sequence', 'host_instance_id', 'source_mount', 'created_at', 'code', 'point_ids', 'message'])\n && isMountRef(value.source_mount)\n && codes.includes(value.code as AttentionQueryErrorCode)\n && Array.isArray(value.point_ids) && value.point_ids.length <= 4096 && value.point_ids.every(id => isNonEmptyString(id, 160))\n && (value.message === undefined || isNonEmptyString(value.message, 512))\n}\n\nfunction isTargetRef(value: unknown): value is AttentionTargetRefV1 {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['snapshot_id', 'target_id', 'host_instance_id', 'mount_id', 'generation', 'path_digest', 'rect', 'label'])\n && isNonEmptyString(value.snapshot_id, 160)\n && isNonEmptyString(value.target_id, 160)\n && isNonEmptyString(value.host_instance_id, 160)\n && isNonEmptyString(value.mount_id, 160)\n && Number.isSafeInteger(value.generation) && (value.generation as number) >= 0\n && typeof value.path_digest === 'string' && /^sha256:[a-f0-9]{64}$/.test(value.path_digest)\n && isRect(value.rect)\n && isOptionalString(value.label, 256)\n}\n\nexport function isAttentionUiActionMessageV1(value: unknown): value is AttentionUiActionMessageV1 {\n if (!isPlainObject(value) || value.schema !== ATTENTION_UI_ACTION_SCHEMA)\n return false\n const common = isNonEmptyString(value.request_id, 160)\n && isNonEmptyString(value.session_id, 160)\n && isNonEmptyString(value.host_instance_id, 160)\n if (!common)\n return false\n if (value.message_type === 'request') {\n const modes: AttentionUiActionMode[] = ['highlight', 'confirm', 'select', 'capture_visual']\n const targets = Array.isArray(value.targets) ? value.targets : undefined\n const targetsValid = targets !== undefined && targets.length <= 32 && targets.every(isTargetRef)\n const zeroTargetSelection = targetsValid && targets.length === 0\n && value.mode === 'select' && value.capture_region === true\n const capture = isPlainObject(value.capture)\n && hasOnlyKeys(value.capture, ['scope', 'region', 'allow_adjustment', 'allow_viewport_choice', 'format'])\n && ['target', 'region', 'viewport'].includes(value.capture.scope as string)\n && (value.capture.region === undefined || isRect(value.capture.region))\n && (value.capture.scope !== 'region' || isRect(value.capture.region))\n && typeof value.capture.allow_adjustment === 'boolean'\n && typeof value.capture.allow_viewport_choice === 'boolean'\n && (value.capture.format === 'image/png' || value.capture.format === 'image/webp')\n const captureRequest = value.mode === 'capture_visual'\n const lifetime = Date.parse(value.expires_at as string) - Date.parse(value.created_at as string)\n return hasOnlyKeys(value, ['schema', 'message_type', 'action_id', 'request_id', 'session_id', 'host_instance_id', 'created_at', 'expires_at', 'mode', 'prompt', 'targets', 'allow_pointer', 'allow_keyboard', 'capture_region', 'capture'])\n && isNonEmptyString(value.action_id, 160)\n && isIsoTimestamp(value.created_at)\n && isIsoTimestamp(value.expires_at)\n // The Host times a request from its receipt, so the lifetime must be\n // positive and within the broker's maximum.\n && lifetime > 0 && lifetime <= ATTENTION_UI_ACTION_MAX_TTL_MS\n && modes.includes(value.mode as AttentionUiActionMode)\n && isOptionalString(value.prompt, 512)\n && targetsValid\n && (targets.length > 0 || zeroTargetSelection)\n && typeof value.allow_pointer === 'boolean'\n && typeof value.allow_keyboard === 'boolean'\n && (captureRequest\n ? capture && value.capture_region === undefined\n : typeof value.capture_region === 'boolean' && value.capture === undefined)\n }\n if (value.message_type !== 'result')\n return false\n const statuses: AttentionUiActionStatus[] = ['selected', 'confirmed', 'prepared', 'cancelled', 'denied', 'rejected', 'expired', 'stale', 'disconnected', 'permission-denied', 'unavailable', 'error']\n if (!hasOnlyKeys(value, ['schema', 'message_type', 'result_id', 'in_reply_to_action_id', 'request_id', 'session_id', 'host_instance_id', 'completed_at', 'status', 'selected_target', 'prepared_file', 'reason'])\n || !isNonEmptyString(value.result_id, 160)\n || !isNonEmptyString(value.in_reply_to_action_id, 160)\n || !isIsoTimestamp(value.completed_at)\n || !statuses.includes(value.status as AttentionUiActionStatus)\n || (value.reason !== undefined && !isOptionalString(value.reason, 512))) {\n return false\n }\n const selected = value.status === 'selected' || value.status === 'confirmed'\n const prepared = value.status === 'prepared'\n const preparedFile = value.prepared_file\n const preparedFileValid = isPlainObject(preparedFile)\n && hasOnlyKeys(preparedFile, ['uuid', 'name', 'mime_type', 'byte_size', 'sha256', 'scope'])\n && isNonEmptyString(preparedFile.uuid, 160)\n && isNonEmptyString(preparedFile.name, 256)\n && (preparedFile.mime_type === 'image/png' || preparedFile.mime_type === 'image/webp')\n && Number.isSafeInteger(preparedFile.byte_size) && (preparedFile.byte_size as number) > 0 && (preparedFile.byte_size as number) <= ATTENTION_VISUAL_MAX_BYTES\n && typeof preparedFile.sha256 === 'string' && /^sha256:[a-f0-9]{64}$/.test(preparedFile.sha256)\n && ['target', 'region', 'viewport'].includes(preparedFile.scope as string)\n if (selected)\n return isTargetRef(value.selected_target) && value.prepared_file === undefined\n if (prepared)\n return value.selected_target === undefined && preparedFileValid\n return value.selected_target === undefined && value.prepared_file === undefined\n}\n\nexport function isAttentionVisualContextV1(value: unknown): value is AttentionVisualContextV1 {\n if (!isPlainObject(value)\n || !hasOnlyKeys(value, ['schema', 'capture_id', 'snapshot_id', 'host_instance_id', 'created_at', 'expires_at', 'candidate_ids', 'region', 'media', 'reference', 'authorization', 'redactions_applied'])\n || value.schema !== ATTENTION_VISUAL_CONTEXT_SCHEMA\n || !isNonEmptyString(value.capture_id, 160)\n || !isNonEmptyString(value.snapshot_id, 160)\n || !isNonEmptyString(value.host_instance_id, 160)\n || !isIsoTimestamp(value.created_at)\n || !isIsoTimestamp(value.expires_at)\n || !Array.isArray(value.candidate_ids)\n || value.candidate_ids.length === 0\n || value.candidate_ids.length > ATTENTION_MAX_CANDIDATES\n || new Set(value.candidate_ids).size !== value.candidate_ids.length\n || !value.candidate_ids.every(id => isNonEmptyString(id, 160))\n || !isRect(value.region)\n || !Number.isSafeInteger(value.redactions_applied)\n || (value.redactions_applied as number) < 0\n || (value.redactions_applied as number) > 4096) {\n return false\n }\n if (!isPlainObject(value.media)\n || !hasOnlyKeys(value.media, ['content_type', 'content_bytes', 'content_hash', 'pixel_width', 'pixel_height'])\n || (value.media.content_type !== 'image/png' && value.media.content_type !== 'image/webp')\n || !Number.isSafeInteger(value.media.content_bytes)\n || (value.media.content_bytes as number) < 1\n || (value.media.content_bytes as number) > ATTENTION_VISUAL_MAX_BYTES\n || typeof value.media.content_hash !== 'string'\n || !/^sha256:[a-f0-9]{64}$/.test(value.media.content_hash)\n || !Number.isSafeInteger(value.media.pixel_width)\n || (value.media.pixel_width as number) < 1\n || (value.media.pixel_width as number) > ATTENTION_VISUAL_MAX_WIDTH_PX\n || !Number.isSafeInteger(value.media.pixel_height)\n || (value.media.pixel_height as number) < 1\n || (value.media.pixel_height as number) > ATTENTION_VISUAL_MAX_HEIGHT_PX\n || (value.media.pixel_width as number) * (value.media.pixel_height as number) > ATTENTION_VISUAL_MAX_PIXELS) {\n return false\n }\n return isPlainObject(value.reference)\n && hasOnlyKeys(value.reference, ['kind', 'opaque_id'])\n && value.reference.kind === 'upload'\n && isNonEmptyString(value.reference.opaque_id, 160)\n && isPlainObject(value.authorization)\n && hasOnlyKeys(value.authorization, ['scope', 'session_id', 'audience', 'expires_at'])\n && value.authorization.scope === 'session'\n && isNonEmptyString(value.authorization.session_id, 160)\n && value.authorization.audience === 'agent-context'\n && isIsoTimestamp(value.authorization.expires_at)\n && value.authorization.expires_at === value.expires_at\n}\n\nexport function matchesAttentionQueryResult(\n request: AttentionPointQueryV1,\n result: AttentionPointQueryResultV1 | AttentionPointQueryErrorV1,\n onMismatch?: (reason: string) => void,\n): boolean {\n const mismatch = (reason: string): false => {\n onMismatch?.(reason)\n return false\n }\n if (request.request_id !== result.request_id\n || request.snapshot_id !== result.snapshot_id\n || request.sequence !== result.sequence\n || request.host_instance_id !== result.host_instance_id\n || !sameMount(request.target_mount, result.source_mount)) {\n return mismatch('identity')\n }\n const pointIds = new Set(request.points.map(point => point.point_id))\n if (result.message_type === 'query-error') {\n return new Set(result.point_ids).size === result.point_ids.length\n && result.point_ids.every(pointId => pointIds.has(pointId))\n }\n\n const observationPointIds = new Set([\n ...(result.observations?.pointer ? [result.observations.pointer.event_id] : []),\n ...(result.observations?.focus ? [result.observations.focus.event_id] : []),\n ...(result.observations?.focus_state?.focus ? [result.observations.focus_state.focus.event_id] : []),\n ...(result.observations?.recent_events.map(event => event.event_id) ?? []),\n ])\n const candidateIds = new Set<string>()\n for (const candidate of result.candidates) {\n if (candidateIds.has(candidate.target_id)\n || candidate.geometry.coordinate_space.mount_id !== result.source_mount.mount_id\n || candidate.geometry.coordinate_space.generation !== result.source_mount.generation\n || candidate.sample_point_ids.length === 0\n || new Set(candidate.sample_point_ids).size !== candidate.sample_point_ids.length\n || !candidate.sample_point_ids.every(pointId => pointIds.has(pointId) || observationPointIds.has(pointId))) {\n return mismatch('candidate-samples')\n }\n candidateIds.add(candidate.target_id)\n }\n\n if (result.point_results.length !== request.points.length\n || !result.point_results.every(pointResult => pointIds.has(pointResult.point_id))\n || new Set(result.point_results.map(pointResult => pointResult.point_id)).size !== request.points.length) {\n return mismatch('point-set')\n }\n\n const pointsByCandidate = new Map<string, Set<string>>()\n for (const pointResult of result.point_results) {\n if (!pointResult.candidate_ids.every(candidateId => candidateIds.has(candidateId)))\n return mismatch('point-candidate-reference')\n const partial = pointResult.omissions.length > 0\n if (pointResult.complete === partial\n || (pointResult.complete && pointResult.status === 'partial')\n || (!pointResult.complete && pointResult.status !== 'partial' && pointResult.status !== 'failed')\n || (pointResult.status === 'hit' && pointResult.candidate_ids.length === 0)\n || (pointResult.status === 'empty' && pointResult.candidate_ids.length > 0)) {\n return mismatch('point-state')\n }\n for (const candidateId of pointResult.candidate_ids) {\n const mapped = pointsByCandidate.get(candidateId) ?? new Set<string>()\n mapped.add(pointResult.point_id)\n pointsByCandidate.set(candidateId, mapped)\n }\n }\n\n const addObservationCandidate = (candidateId: string, eventId: string) => {\n const mapped = pointsByCandidate.get(candidateId) ?? new Set<string>()\n mapped.add(eventId)\n pointsByCandidate.set(candidateId, mapped)\n }\n if (result.observations?.pointer) {\n for (const candidateId of result.observations.pointer.candidate_ids) {\n if (!candidateIds.has(candidateId))\n return mismatch('pointer-candidate-reference')\n addObservationCandidate(candidateId, result.observations.pointer.event_id)\n }\n }\n if (result.observations?.focus?.candidate_id) {\n if (!candidateIds.has(result.observations.focus.candidate_id))\n return mismatch('focus-candidate-reference')\n addObservationCandidate(result.observations.focus.candidate_id, result.observations.focus.event_id)\n }\n if (result.observations?.focus_state?.focus?.candidate_id) {\n const retained = result.observations.focus_state.focus\n if (!candidateIds.has(retained.candidate_id!))\n return mismatch('focus-state-candidate-reference')\n addObservationCandidate(retained.candidate_id!, retained.event_id)\n }\n for (const event of result.observations?.recent_events ?? []) {\n for (const candidateId of event.candidate_ids) {\n if (!candidateIds.has(candidateId))\n return mismatch('recent-event-candidate-reference')\n addObservationCandidate(candidateId, event.event_id)\n }\n }\n\n for (const candidate of result.candidates) {\n const mapped = pointsByCandidate.get(candidate.target_id)\n if (!mapped || mapped.size !== candidate.sample_point_ids.length || candidate.sample_point_ids.some(pointId => !mapped.has(pointId)))\n return mismatch('candidate-sample-relation')\n }\n\n const complete = result.omissions.length === 0 && result.point_results.every(pointResult => pointResult.complete)\n return result.complete === complete || mismatch('result-completeness')\n}\n\nexport function matchesAttentionSelectionQueryResult(request: AttentionSelectionQueryRequestV1, result: AttentionSelectionQueryResultV1 | AttentionSelectionQueryErrorV1): boolean {\n return request.request_id === result.request_id\n && request.snapshot_id === result.snapshot_id\n && request.sequence === result.sequence\n && request.host_instance_id === result.host_instance_id\n && sameMount(request.target_mount, result.source_mount)\n && (result.message_type === 'selection-query-error' || result.complete === (result.omissions.length === 0))\n}\n\nexport function isAttentionSnapshotV1(value: unknown): value is AttentionSnapshotV1 {\n if (!isPlainObject(value) || value.schema !== ATTENTION_CONTEXT_SCHEMA)\n return false\n if (!hasOnlyKeys(value, ['schema', 'snapshot_id', 'host_instance_id', 'mount_generation', 'created_at', 'coordinate_space', 'capture', 'pointer', 'focus', 'selection', 'recent_events', 'candidates', 'omissions']))\n return false\n if (!isNonEmptyString(value.snapshot_id, 128) || !isNonEmptyString(value.host_instance_id, 160))\n return false\n if (!Number.isSafeInteger(value.mount_generation) || (value.mount_generation as number) < 0 || !isIsoTimestamp(value.created_at))\n return false\n if (!isPlainObject(value.coordinate_space)\n || !hasOnlyKeys(value.coordinate_space, ['kind', 'width', 'height', 'device_pixel_ratio'])\n || value.coordinate_space.kind !== 'host-viewport'\n || !isFiniteNumber(value.coordinate_space.width)\n || value.coordinate_space.width < 0\n || !isFiniteNumber(value.coordinate_space.height)\n || value.coordinate_space.height < 0\n || !isFiniteNumber(value.coordinate_space.device_pixel_ratio)\n || value.coordinate_space.device_pixel_ratio <= 0) {\n return false\n }\n if (!isPlainObject(value.capture)\n || !hasOnlyKeys(value.capture, ['radius_css_px', 'grid_step_css_px', 'sampled_points', 'points', 'duration_ms', 'complete'])\n || !isFiniteNumber(value.capture.radius_css_px)\n || value.capture.radius_css_px < 0 || value.capture.radius_css_px > 100\n || !isFiniteNumber(value.capture.grid_step_css_px)\n || value.capture.grid_step_css_px <= 0 || value.capture.grid_step_css_px > 100\n || !Number.isSafeInteger(value.capture.sampled_points)\n || (value.capture.sampled_points as number) < 0 || (value.capture.sampled_points as number) > 4096\n || !Array.isArray(value.capture.points)\n || value.capture.points.length !== value.capture.sampled_points\n || value.capture.points.length > 4096\n || !value.capture.points.every(isQueryPoint)\n || !isFiniteNumber(value.capture.duration_ms)\n || value.capture.duration_ms < 0\n || typeof value.capture.complete !== 'boolean') {\n return false\n }\n const pointIds = new Set<string>()\n for (const point of value.capture.points as AttentionQueryPoint[]) {\n if (pointIds.has(point.point_id))\n return false\n pointIds.add(point.point_id)\n }\n if (value.pointer !== undefined && !isObservedEvent(value.pointer))\n return false\n if (value.focus !== undefined && !isAttentionFocus(value.focus))\n return false\n if (value.selection !== undefined && !isAttentionSelection(value.selection))\n return false\n if (!Array.isArray(value.recent_events) || value.recent_events.length > 32 || !value.recent_events.every(isObservedEvent))\n return false\n const observationPointIds = new Set([\n ...(value.pointer ? [value.pointer.event_id] : []),\n ...(value.focus ? [value.focus.event_id] : []),\n ...value.recent_events.map(event => event.event_id),\n ])\n if (!Array.isArray(value.candidates) || value.candidates.length > 128)\n return false\n for (const candidate of value.candidates) {\n if (!isPlainObject(candidate)\n || !hasOnlyKeys(candidate, ['target_id', 'path', 'rect', 'clip_rect', 'sample_point_ids', 'occluded', 'summary', 'provenance', 'action_ref'])\n || !isNonEmptyString(candidate.target_id, 128)\n || !isPath(candidate.path)\n || !isRect(candidate.rect)\n || (candidate.clip_rect !== undefined && !isRect(candidate.clip_rect))\n || !Array.isArray(candidate.sample_point_ids)\n || candidate.sample_point_ids.length === 0\n || candidate.sample_point_ids.length > 4096\n || new Set(candidate.sample_point_ids).size !== candidate.sample_point_ids.length\n || !candidate.sample_point_ids.every(id => isNonEmptyString(id, 128) && (pointIds.has(id) || observationPointIds.has(id)))\n || typeof candidate.occluded !== 'boolean'\n || !isSummary(candidate.summary)\n || (candidate.provenance !== undefined && !isProvenance(candidate.provenance))) {\n return false\n }\n if (candidate.action_ref !== undefined) {\n const deepest = candidate.path[candidate.path.length - 1]\n if (!isTargetRef(candidate.action_ref)\n || candidate.action_ref.snapshot_id !== value.snapshot_id\n || candidate.action_ref.target_id !== candidate.target_id\n || candidate.action_ref.host_instance_id !== value.host_instance_id\n || candidate.action_ref.mount_id !== deepest.mount_id\n || candidate.action_ref.generation !== deepest.generation\n || candidate.action_ref.rect.x !== candidate.rect.x\n || candidate.action_ref.rect.y !== candidate.rect.y\n || candidate.action_ref.rect.width !== candidate.rect.width\n || candidate.action_ref.rect.height !== candidate.rect.height) {\n return false\n }\n }\n }\n const candidateIds = new Set(value.candidates.map(candidate => candidate.target_id))\n if (value.pointer?.candidate_ids.some(candidateId => !candidateIds.has(candidateId))\n || value.recent_events.some(event => event.candidate_ids.some(candidateId => !candidateIds.has(candidateId)))\n || (value.focus?.candidate_id !== undefined && !candidateIds.has(value.focus.candidate_id))) {\n return false\n }\n return Array.isArray(value.omissions) && value.omissions.length <= 128 && value.omissions.every(isOmission)\n}\n\nfunction compactAssert(condition: unknown): asserts condition {\n if (!condition)\n throw new RangeError('Invalid or excessive compact Attention context')\n}\n\nfunction compactJson(value: unknown): string {\n return canonicalJson(value as JsonValue)\n}\n\nfunction omitCompactKeys(value: Record<string, unknown>, excluded: string[]): Record<string, unknown> {\n return Object.fromEntries(Object.entries(value).filter(([key]) => !excluded.includes(key)))\n}\n\n/** Structural expansion only. Use the async integrity variant before trusting action digests. */\nexport function decodeAttentionSnapshotV2(value: unknown, maxExpandedBytes = ATTENTION_MAX_EXPANDED_BYTES): AttentionSnapshotV1 {\n assertJsonValue(value)\n compactAssert(isPlainObject(value) && value.schema === ATTENTION_CONTEXT_V2_SCHEMA\n && hasOnlyKeys(value, ['schema', 'snapshot_id', 'host_instance_id', 'mount_generation', 'created_at', 'coordinate_space', 'capture', 'pointer', 'focus', 'selection', 'recent_events', 'candidates', 'omissions', 'path_dictionary'])\n && Array.isArray(value.path_dictionary) && value.path_dictionary.length <= ATTENTION_V2_MAX_DICTIONARY\n && Array.isArray(value.candidates) && value.candidates.length <= ATTENTION_MAX_CANDIDATES\n && Array.isArray(value.recent_events) && value.recent_events.length <= 32\n && Array.isArray(value.omissions) && value.omissions.length <= ATTENTION_MAX_SNAPSHOT_OMISSIONS\n && (value.pointer === undefined || isObservedEvent(value.pointer))\n && (value.focus === undefined || isPlainObject(value.focus)))\n const dictionary: unknown[] = value.path_dictionary\n const recentEvents: unknown[] = value.recent_events\n compactAssert(dictionary.every(isPathSegment) && recentEvents.every(isObservedEvent))\n const segments = dictionary.map(compactJson)\n compactAssert(new Set(segments).size === segments.length)\n const capture = value.capture\n compactAssert(isPlainObject(capture)\n && hasOnlyKeys(capture, ['radius_css_px', 'grid_step_css_px', 'sampled_points', 'points', 'point_encoding', 'duration_ms', 'complete'])\n && isFiniteNumber(capture.radius_css_px) && capture.radius_css_px >= 0 && capture.radius_css_px <= 100\n && isFiniteNumber(capture.grid_step_css_px) && capture.grid_step_css_px >= 1 && capture.grid_step_css_px <= 100\n && Number.isSafeInteger(capture.sampled_points) && (capture.sampled_points as number) >= 0 && (capture.sampled_points as number) <= ATTENTION_MAX_QUERY_POINTS\n && (capture.points === undefined) !== (capture.point_encoding === undefined))\n const observationIds = new Set([\n ...(value.pointer ? [value.pointer.event_id] : []),\n ...(value.focus ? [value.focus.event_id] : []),\n ...recentEvents.map(event => event.event_id),\n ])\n const points: AttentionQueryPoint[] = []\n const candidates: AttentionCandidate[] = []\n const output = {\n ...omitCompactKeys(value, ['path_dictionary', 'capture', 'candidates', 'focus', 'selection']),\n schema: ATTENTION_CONTEXT_SCHEMA,\n capture: { ...omitCompactKeys(capture, ['point_encoding', 'points']), points },\n candidates,\n ...(value.focus ? { focus: { ...omitCompactKeys(value.focus, ['path_indices']), path: [] as AttentionPathSegment[] } } : {}),\n } as unknown as AttentionSnapshotV1\n compactAssert(!value.focus || value.focus.path === undefined)\n let used = utf8ByteLength(compactJson(output))\n const budget = Math.min(maxExpandedBytes, ATTENTION_MAX_EXPANDED_BYTES)\n compactAssert(Number.isSafeInteger(budget) && used <= budget)\n const charge = (bytes: number) => {\n compactAssert(bytes <= budget - used)\n used += bytes\n }\n const pointIds = new Set<string>()\n const appendPoint = (point: unknown) => {\n compactAssert(isQueryPoint(point) && !pointIds.has(point.point_id) && !observationIds.has(point.point_id) && points.length < ATTENTION_MAX_QUERY_POINTS)\n charge(utf8ByteLength(compactJson(point)) + (points.length ? 1 : 0))\n points.push(point)\n pointIds.add(point.point_id)\n }\n if (capture.points !== undefined) {\n compactAssert(Array.isArray(capture.points) && capture.points.length <= ATTENTION_MAX_QUERY_POINTS)\n capture.points.forEach(appendPoint)\n }\n else {\n const encoding = capture.point_encoding\n compactAssert(isPlainObject(encoding) && hasOnlyKeys(encoding, ['kind', 'origin', 'overrides', 'additional_points'])\n && encoding.kind === 'css-euclidean-grid.v1' && isPoint(encoding.origin)\n && (encoding.overrides === undefined || (Array.isArray(encoding.overrides) && encoding.overrides.length <= ATTENTION_MAX_QUERY_POINTS))\n && (encoding.additional_points === undefined || (Array.isArray(encoding.additional_points) && encoding.additional_points.length <= ATTENTION_MAX_QUERY_POINTS)))\n const extent = Math.floor(capture.radius_css_px / capture.grid_step_css_px)\n compactAssert((2 * extent + 1) ** 2 <= ATTENTION_MAX_QUERY_POINTS)\n const overrides = new Map<number, AttentionQueryPoint>()\n for (const override of encoding.overrides ?? []) {\n compactAssert(isPlainObject(override) && hasOnlyKeys(override, ['index', 'point'])\n && Number.isSafeInteger(override.index) && (override.index as number) >= 0\n && (override.index as number) < ATTENTION_MAX_QUERY_POINTS && !overrides.has(override.index as number) && isQueryPoint(override.point))\n overrides.set(override.index as number, override.point)\n }\n for (let yIndex = -extent; yIndex <= extent; yIndex++) {\n for (let xIndex = -extent; xIndex <= extent; xIndex++) {\n const x = xIndex * capture.grid_step_css_px\n const y = yIndex * capture.grid_step_css_px\n if (x * x + y * y <= capture.radius_css_px * capture.radius_css_px) {\n const index = points.length\n appendPoint(overrides.get(index) ?? { point_id: `p${index}`, x: encoding.origin.x + x, y: encoding.origin.y + y })\n overrides.delete(index)\n }\n }\n }\n compactAssert(overrides.size === 0)\n for (const point of encoding.additional_points ?? [])\n appendPoint(point)\n }\n compactAssert(points.length === capture.sampled_points)\n const seenSegments = new Set<number>()\n const path = (indices: unknown): AttentionPathSegment[] => {\n compactAssert(Array.isArray(indices) && indices.length > 0 && indices.length <= ATTENTION_MAX_PATH_SEGMENTS)\n const result: AttentionPathSegment[] = []\n for (const index of indices) {\n compactAssert(Number.isSafeInteger(index) && index >= 0 && index < dictionary.length)\n if (!seenSegments.has(index)) {\n compactAssert(index === seenSegments.size)\n seenSegments.add(index)\n }\n charge(utf8ByteLength(segments[index]) + (result.length ? 1 : 0))\n result.push(dictionary[index])\n }\n return result\n }\n let memberships = 0\n const targets = new Set<string>()\n for (const candidate of value.candidates) {\n compactAssert(isPlainObject(candidate) && hasOnlyKeys(candidate, ['target_id', 'path_indices', 'sample_refs', 'rect', 'clip_rect', 'occluded', 'summary', 'provenance', 'action_ref'])\n && isNonEmptyString(candidate.target_id, 128) && !targets.has(candidate.target_id)\n && Array.isArray(candidate.sample_refs) && candidate.sample_refs.length > 0 && candidate.sample_refs.length <= ATTENTION_MAX_QUERY_POINTS)\n const expanded = { ...omitCompactKeys(candidate, ['path_indices', 'sample_refs']), path: [] as AttentionPathSegment[], sample_point_ids: [] as string[] }\n charge(utf8ByteLength(compactJson(expanded)) + (candidates.length ? 1 : 0))\n expanded.path = path(candidate.path_indices)\n const seen = new Set<string>()\n const append = (id: string) => {\n compactAssert(!seen.has(id) && seen.size < ATTENTION_MAX_QUERY_POINTS && memberships < ATTENTION_V2_MAX_MEMBERSHIPS)\n charge(utf8ByteLength(JSON.stringify(id)) + (seen.size ? 1 : 0))\n expanded.sample_point_ids.push(id)\n seen.add(id)\n memberships++\n }\n for (const ref of candidate.sample_refs) {\n if (typeof ref === 'string') {\n compactAssert(observationIds.has(ref))\n append(ref)\n }\n else if (typeof ref === 'number') {\n compactAssert(Number.isSafeInteger(ref) && ref >= 0 && ref < points.length)\n append(points[ref].point_id)\n }\n else {\n compactAssert(Array.isArray(ref) && ref.length === 2)\n const [start, count] = ref\n compactAssert(isFiniteNumber(start) && Number.isSafeInteger(start) && start >= 0\n && isFiniteNumber(count) && Number.isSafeInteger(count) && count > 0 && start + count <= points.length\n && count <= ATTENTION_MAX_QUERY_POINTS - seen.size && count <= ATTENTION_V2_MAX_MEMBERSHIPS - memberships)\n for (let index = start; index < start + count; index++)\n append(points[index].point_id)\n }\n }\n candidates.push(expanded as unknown as AttentionCandidate)\n targets.add(candidate.target_id)\n }\n if (output.focus)\n output.focus.path = path(value.focus!.path_indices)\n if (value.selection !== undefined) {\n compactAssert(isPlainObject(value.selection))\n if (hasOwn(value.selection, 'anchor_path') || hasOwn(value.selection, 'focus_path')) {\n compactAssert(isAttentionSelection(value.selection))\n output.selection = value.selection\n }\n else {\n compactAssert(hasOnlyKeys(value.selection, ['selection_id', 'selected_at', 'kind', 'collapsed', 'direction', 'text', 'anchor_path_indices', 'focus_path_indices', 'ranges']))\n output.selection = {\n ...omitCompactKeys(value.selection, ['anchor_path_indices', 'focus_path_indices']),\n anchor_path: path(value.selection.anchor_path_indices),\n focus_path: path(value.selection.focus_path_indices),\n } as unknown as AttentionSelection\n }\n charge(utf8ByteLength(compactJson(output)) - used)\n }\n compactAssert(seenSegments.size === dictionary.length && isAttentionSnapshotV1(output) && !containsForbiddenContextKey(output))\n compactAssert(utf8ByteLength(compactJson(output)) === used)\n return JSON.parse(compactJson(output)) as AttentionSnapshotV1\n}\n\nexport function isAttentionSnapshotV2(value: unknown): value is AttentionSnapshotV2 {\n try {\n decodeAttentionSnapshotV2(value)\n return true\n }\n catch {\n return false\n }\n}\n\nexport async function decodeAttentionSnapshotV2Integrity(value: unknown, maxExpandedBytes = ATTENTION_MAX_EXPANDED_BYTES): Promise<AttentionSnapshotV1> {\n const snapshot = decodeAttentionSnapshotV2(value, maxExpandedBytes)\n for (const candidate of snapshot.candidates)\n compactAssert(!candidate.action_ref || await attentionPathDigestMatches(candidate.path, candidate.action_ref.path_digest))\n return snapshot\n}\n\n/** Deterministic compact encoding; a lattice is selected only after exact reconstruction. */\nexport async function encodeAttentionSnapshotV2(snapshot: AttentionSnapshotV1): Promise<AttentionSnapshotV2> {\n compactAssert(isAttentionSnapshotV1(snapshot) && !containsForbiddenContextKey(snapshot))\n const original = JSON.parse(JSON.stringify(snapshot)) as AttentionSnapshotV1\n const dictionary: AttentionPathSegment[] = []\n const indices = new Map<string, number>()\n const path = (segments: AttentionPathSegment[]) => segments.map((segment) => {\n const encoded = compactJson(segment)\n if (!indices.has(encoded)) {\n indices.set(encoded, dictionary.length)\n dictionary.push(segment)\n }\n return indices.get(encoded)!\n })\n const pointIndices = new Map(original.capture.points.map((point, index) => [point.point_id, index]))\n const candidates = original.candidates.map((candidate) => {\n const refs: AttentionSampleRefV2[] = []\n for (let index = 0; index < candidate.sample_point_ids.length;) {\n const id = candidate.sample_point_ids[index]\n const start = pointIndices.get(id)\n if (start === undefined) {\n refs.push(id)\n index++\n continue\n }\n let count = 1\n while (pointIndices.get(candidate.sample_point_ids[index + count]) === start + count)\n count++\n refs.push(count > 1 ? [start, count] : start)\n index += count\n }\n return { ...omitCompactKeys(candidate as unknown as Record<string, unknown>, ['path', 'sample_point_ids']), path_indices: path(candidate.path), sample_refs: refs } as AttentionCandidateV2\n })\n const focus = original.focus ? { ...omitCompactKeys(original.focus as unknown as Record<string, unknown>, ['path']), path_indices: path(original.focus.path) } as AttentionSnapshotV2['focus'] : undefined\n const capturePoints = original.capture.points\n let capture: AttentionSnapshotV2['capture'] = original.capture\n const origin = original.pointer?.point\n if (origin) {\n try {\n const grid = createAttentionSampleGrid(origin, capture.radius_css_px, capture.grid_step_css_px)\n if (grid.length <= capturePoints.length) {\n const overrides = grid.flatMap((point, index) => compactJson(point) === compactJson(capturePoints[index]) ? [] : [{ index, point: capturePoints[index] }])\n const additional = capturePoints.slice(grid.length)\n const point_encoding: AttentionGridEncodingV2 = { kind: 'css-euclidean-grid.v1', origin, ...(overrides.length ? { overrides } : {}), ...(additional.length ? { additional_points: additional } : {}) }\n const encodedCapture = { ...omitCompactKeys(capture as unknown as Record<string, unknown>, ['points']), point_encoding } as AttentionSnapshotV2['capture']\n if (utf8ByteLength(compactJson(encodedCapture)) < utf8ByteLength(compactJson(capture)))\n capture = encodedCapture\n }\n }\n catch {\n // An irregular or unrepresentable neighborhood keeps the explicit v1 points.\n }\n }\n const encoded = { ...original, schema: ATTENTION_CONTEXT_V2_SCHEMA, path_dictionary: dictionary, capture, candidates, ...(focus ? { focus } : {}) } as AttentionSnapshotV2\n const reconstructed = await decodeAttentionSnapshotV2Integrity(encoded)\n compactAssert(compactJson(reconstructed) === compactJson(original))\n return encoded\n}\n\nfunction packAttentionRectV3(rect: AttentionRect): AttentionRectTupleV3 {\n return [rect.x, rect.y, rect.width, rect.height]\n}\n\nfunction unpackAttentionRectV3(value: unknown): AttentionRect {\n compactAssert(Array.isArray(value) && value.length === 4\n && [0, 1, 2, 3].every(index => hasOwn(value, index))\n && value.every(isFiniteNumber)\n && value[2] >= 0 && value[3] >= 0)\n return { x: value[0], y: value[1], width: value[2], height: value[3] }\n}\n\nfunction packAttentionPathSegmentV3(segment: AttentionPathSegment): AttentionPathSegmentV3 {\n const attributes = omitCompactKeys(segment as unknown as Record<string, unknown>, [\n 'kind',\n 'mount_id',\n 'generation',\n 'rect',\n 'clip_rect',\n 'local_to_parent',\n ]) as unknown as AttentionPathAttributesV3\n if (segment.rect)\n attributes.rect = packAttentionRectV3(segment.rect)\n if (segment.clip_rect)\n attributes.clip_rect = packAttentionRectV3(segment.clip_rect)\n if (segment.local_to_parent)\n attributes.local_to_parent = [...segment.local_to_parent.matrix] as AttentionTransform['matrix']\n return [segment.kind, segment.mount_id, segment.generation, attributes]\n}\n\nfunction unpackAttentionPathSegmentV3(value: unknown): AttentionPathSegment {\n compactAssert(Array.isArray(value) && value.length === 4\n && [0, 1, 2, 3].every(index => hasOwn(value, index)))\n const [kind, mountId, generation, attributes] = value\n compactAssert(isPlainObject(attributes)\n && hasOnlyKeys(attributes, ['label', 'panel_id', 'surface_id', 'artifact_id', 'page_id', 'package_id', 'tag_name', 'selector_hint', 'frame_origin', 'rect', 'clip_rect', 'local_to_parent', 'coordinate_quality']))\n const matrix = attributes.local_to_parent\n compactAssert(matrix === undefined || (Array.isArray(matrix) && matrix.length === 16\n && Array.from({ length: 16 }, (_unused, index) => index).every(index => hasOwn(matrix, index))\n && matrix.every(isFiniteNumber)))\n const segment = {\n ...omitCompactKeys(attributes, ['rect', 'clip_rect', 'local_to_parent']),\n kind,\n mount_id: mountId,\n generation,\n ...(attributes.rect !== undefined ? { rect: unpackAttentionRectV3(attributes.rect) } : {}),\n ...(attributes.clip_rect !== undefined ? { clip_rect: unpackAttentionRectV3(attributes.clip_rect) } : {}),\n ...(matrix !== undefined\n ? { local_to_parent: { matrix, convention: 'dommatrix-column-major', direction: 'local-to-parent' } }\n : {}),\n }\n compactAssert(isPathSegment(segment))\n return segment\n}\n\n/** Expands the lossless v3 path tuples before all existing v2 structural and byte-budget validation. */\nexport function decodeAttentionSnapshotV3(value: unknown, maxExpandedBytes = ATTENTION_MAX_EXPANDED_BYTES): AttentionSnapshotV1 {\n assertJsonValue(value)\n compactAssert(isPlainObject(value) && value.schema === ATTENTION_CONTEXT_V3_SCHEMA\n && Array.isArray(value.path_dictionary) && value.path_dictionary.length <= ATTENTION_V2_MAX_DICTIONARY)\n const normalized = {\n ...value,\n schema: ATTENTION_CONTEXT_V2_SCHEMA,\n path_dictionary: value.path_dictionary.map(unpackAttentionPathSegmentV3),\n }\n return decodeAttentionSnapshotV2(normalized, maxExpandedBytes)\n}\n\nexport function isAttentionSnapshotV3(value: unknown): value is AttentionSnapshotV3 {\n try {\n decodeAttentionSnapshotV3(value)\n return true\n }\n catch {\n return false\n }\n}\n\nexport async function decodeAttentionSnapshotV3Integrity(value: unknown, maxExpandedBytes = ATTENTION_MAX_EXPANDED_BYTES): Promise<AttentionSnapshotV1> {\n const snapshot = decodeAttentionSnapshotV3(value, maxExpandedBytes)\n for (const candidate of snapshot.candidates)\n compactAssert(!candidate.action_ref || await attentionPathDigestMatches(candidate.path, candidate.action_ref.path_digest))\n return snapshot\n}\n\n/** Deterministic lossless v3 encoding with packed path-segment geometry. */\nexport async function encodeAttentionSnapshotV3(snapshot: AttentionSnapshotV1): Promise<AttentionSnapshotV3> {\n const v2 = await encodeAttentionSnapshotV2(snapshot)\n const encoded = {\n ...v2,\n schema: ATTENTION_CONTEXT_V3_SCHEMA,\n path_dictionary: v2.path_dictionary.map(packAttentionPathSegmentV3),\n } as AttentionSnapshotV3\n const reconstructed = await decodeAttentionSnapshotV3Integrity(encoded)\n compactAssert(compactJson(reconstructed) === compactJson(snapshot))\n return encoded\n}\n\nexport function decodeAttentionSnapshotV4(value: unknown, maxExpandedBytes = ATTENTION_MAX_EXPANDED_BYTES): AttentionSnapshotV1 {\n assertJsonValue(value)\n compactAssert(isPlainObject(value) && value.schema === ATTENTION_CONTEXT_V4_SCHEMA)\n return decodeAttentionSnapshotV3({ ...value, schema: ATTENTION_CONTEXT_V3_SCHEMA }, maxExpandedBytes)\n}\n\nexport function isAttentionSnapshotV4(value: unknown): value is AttentionSnapshotV4 {\n try {\n decodeAttentionSnapshotV4(value)\n return true\n }\n catch {\n return false\n }\n}\n\nexport async function decodeAttentionSnapshotV4Integrity(value: unknown, maxExpandedBytes = ATTENTION_MAX_EXPANDED_BYTES): Promise<AttentionSnapshotV1> {\n const snapshot = decodeAttentionSnapshotV4(value, maxExpandedBytes)\n for (const candidate of snapshot.candidates)\n compactAssert(!candidate.action_ref || await attentionPathDigestMatches(candidate.path, candidate.action_ref.path_digest))\n return snapshot\n}\n\nexport async function encodeAttentionSnapshotV4(snapshot: AttentionSnapshotV1): Promise<AttentionSnapshotV4> {\n const v3 = await encodeAttentionSnapshotV3(snapshot)\n const dictionary = [...v3.path_dictionary]\n const indices = new Map(dictionary.map((segment, index) => [compactJson(unpackAttentionPathSegmentV3(segment)), index]))\n const path = (segments: AttentionPathSegment[]) => segments.map((segment) => {\n const key = compactJson(segment)\n if (!indices.has(key)) {\n compactAssert(dictionary.length < ATTENTION_V2_MAX_DICTIONARY)\n indices.set(key, dictionary.length)\n dictionary.push(packAttentionPathSegmentV3(segment))\n }\n return indices.get(key)!\n })\n const selection = v3.selection\n ? {\n ...omitCompactKeys(v3.selection as unknown as Record<string, unknown>, ['anchor_path', 'focus_path']),\n anchor_path_indices: path(v3.selection.anchor_path),\n focus_path_indices: path(v3.selection.focus_path),\n } as unknown as AttentionSelectionV4\n : undefined\n const encoded = {\n ...omitCompactKeys(v3 as unknown as Record<string, unknown>, ['selection', 'path_dictionary']),\n schema: ATTENTION_CONTEXT_V4_SCHEMA,\n path_dictionary: dictionary,\n ...(selection ? { selection } : {}),\n } as unknown as AttentionSnapshotV4\n const reconstructed = await decodeAttentionSnapshotV4Integrity(encoded)\n compactAssert(compactJson(reconstructed) === compactJson(snapshot))\n return encoded\n}\n\nconst ATTENTION_FORBIDDEN_KEYS = new Set([\n 'capability_token',\n 'connection_target',\n 'delivery_handle',\n 'broker_pid',\n 'node_handle',\n 'reflection_handle',\n 'result_channel',\n 'runtime_context',\n 'signed_url',\n 'target_token',\n 'ui_action_runtime',\n])\n\nexport function containsForbiddenContextKey(value: unknown, allowVisualAuthorization = false): boolean {\n if (Array.isArray(value))\n return value.some(child => containsForbiddenContextKey(child, allowVisualAuthorization))\n if (!isPlainObject(value))\n return false\n return Object.entries(value).some(([key, child]) => {\n if (ATTENTION_FORBIDDEN_KEYS.has(key))\n return true\n if (key === 'authorization') {\n return !allowVisualAuthorization\n || !isPlainObject(child)\n || !hasOnlyKeys(child, ['scope', 'session_id', 'audience', 'expires_at'])\n || child.scope !== 'session'\n || !isNonEmptyString(child.session_id, 160)\n || child.audience !== 'agent-context'\n || !isIsoTimestamp(child.expires_at)\n }\n return containsForbiddenContextKey(child, allowVisualAuthorization)\n })\n}\n\nfunction compareUtf8(left: string, right: string): number {\n const leftBytes = new TextEncoder().encode(left)\n const rightBytes = new TextEncoder().encode(right)\n const length = Math.min(leftBytes.length, rightBytes.length)\n for (let index = 0; index < length; index++) {\n if (leftBytes[index] !== rightBytes[index])\n return leftBytes[index] - rightBytes[index]\n }\n return leftBytes.length - rightBytes.length\n}\n\nfunction assertJsonValue(value: unknown, depth = 0, seen = new Set<object>()): asserts value is JsonValue {\n if (depth > 32)\n throw new TypeError('Context attachment JSON exceeds the maximum depth of 32')\n if (value === null || typeof value === 'string' || typeof value === 'boolean')\n return\n if (typeof value === 'number') {\n if (!Number.isFinite(value))\n throw new TypeError('Context attachment JSON numbers must be finite')\n return\n }\n if (typeof value !== 'object')\n throw new TypeError('Context attachment content must contain only JSON values')\n if (seen.has(value))\n throw new TypeError('Context attachment JSON must not contain cycles')\n if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)\n throw new TypeError('Context attachment JSON objects must be plain objects')\n\n seen.add(value)\n if (Array.isArray(value)) {\n for (let index = 0; index < value.length; index++) {\n if (!(index in value))\n throw new TypeError('Context attachment JSON arrays must not be sparse')\n assertJsonValue(value[index], depth + 1, seen)\n }\n }\n else {\n for (const child of Object.values(value))\n assertJsonValue(child, depth + 1, seen)\n }\n seen.delete(value)\n}\n\nexport function canonicalJson(value: JsonValue): string {\n assertJsonValue(value)\n if (value === null || typeof value !== 'object')\n return JSON.stringify(value)\n if (Array.isArray(value))\n return `[${value.map(canonicalJson).join(',')}]`\n return `{${Object.keys(value).sort(compareUtf8).map(key => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`\n}\n\nexport function utf8ByteLength(value: string): number {\n return new TextEncoder().encode(value).byteLength\n}\n\nexport async function sha256Json(value: JsonValue): Promise<`sha256:${string}`> {\n return sha256Text(canonicalJson(value))\n}\n\n/** Stable path identity. Geometry is checked at request ingestion and may change while the same mounted target remains live. */\nexport function attentionSemanticPath(path: AttentionPathSegment[]): AttentionPathSegment[] {\n return path.map(({ rect: _rect, clip_rect: _clipRect, local_to_parent: _localToParent, coordinate_quality: _coordinateQuality, ...semantic }) => Object.fromEntries(\n Object.entries(semantic).filter(([, value]) => value !== undefined),\n ) as unknown as AttentionPathSegment)\n}\n\nexport async function sha256AttentionPath(path: AttentionPathSegment[]): Promise<`sha256:${string}`> {\n return sha256Json(attentionSemanticPath(path) as unknown as JsonValue)\n}\n\nasync function attentionPathDigestMatches(path: AttentionPathSegment[], digest: string): Promise<boolean> {\n if (await sha256AttentionPath(path) === digest)\n return true\n return await sha256Json(path as unknown as JsonValue) === digest\n}\n\nexport async function sha256Text(value: string): Promise<`sha256:${string}`> {\n const digest = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(value))\n const hex = Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join('')\n return `sha256:${hex}`\n}\n\nexport async function createContextAttachment<TPayload extends JsonValue>(\n fields: ContextAttachmentInput<TPayload>,\n): Promise<ContextAttachmentEnvelope> {\n const { payload, ...envelope } = fields\n const content = canonicalJson(payload)\n return {\n ...envelope,\n content_type: 'application/json',\n content_bytes: utf8ByteLength(content),\n content_hash: await sha256Text(content),\n content,\n }\n}\n\nexport function parseContextAttachmentContent<TPayload extends JsonValue = JsonValue>(attachment: ContextAttachmentEnvelope): TPayload {\n return JSON.parse(attachment.content) as TPayload\n}\n\nexport function validateContextAttachments(\n value: unknown,\n options: ContextAttachmentValidationOptions = {},\n): ContextAttachmentValidationResult {\n const errors: ContextAttachmentValidationError[] = []\n if (!Array.isArray(value)) {\n return { ok: false, errors: [{ code: 'array-required', path: 'context_attachments', message: 'context_attachments must be an array' }] }\n }\n const maxCount = Math.min(options.maxCount ?? CONTEXT_ATTACHMENTS_MAX_COUNT, CONTEXT_ATTACHMENTS_MAX_COUNT)\n const maxTotalBytes = Math.min(options.maxTotalBytes ?? CONTEXT_ATTACHMENTS_MAX_BYTES, CONTEXT_ATTACHMENTS_MAX_BYTES)\n const maxAttachmentBytes = Math.min(options.maxAttachmentBytes ?? CONTEXT_ATTACHMENT_MAX_BYTES, CONTEXT_ATTACHMENT_MAX_BYTES)\n const totalBytes = utf8ByteLength(JSON.stringify(value))\n if (value.length > maxCount)\n errors.push({ code: 'too-many-attachments', path: 'context_attachments', message: `at most ${maxCount} attachments are allowed` })\n if (totalBytes > maxTotalBytes)\n errors.push({ code: 'total-bytes-exceeded', path: 'context_attachments', message: `attachments exceed ${maxTotalBytes} bytes` })\n\n const ids = new Set<string>()\n let expandedAttentionBytes = 0\n value.forEach((candidate, index) => {\n const path = `context_attachments[${index}]`\n if (!isPlainObject(candidate)\n || !hasOnlyKeys(candidate, ['attachment_id', 'kind', 'version', 'created_at', 'expires_at', 'content_type', 'content_bytes', 'content_hash', 'content'])\n || !isNonEmptyString(candidate.attachment_id, 128)\n || !isNonEmptyString(candidate.kind, 128)\n || !/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/.test(candidate.kind as string)\n || !Number.isSafeInteger(candidate.version)\n || (candidate.version as number) < 1\n || !isIsoTimestamp(candidate.created_at)\n || (candidate.expires_at !== undefined && !isIsoTimestamp(candidate.expires_at))\n || candidate.content_type !== 'application/json'\n || !Number.isSafeInteger(candidate.content_bytes)\n || (candidate.content_bytes as number) < 0\n || (candidate.content_bytes as number) > CONTEXT_ATTACHMENT_MAX_BYTES\n || !isNonEmptyString(candidate.content_hash, 71)\n || typeof candidate.content !== 'string'\n || candidate.content.length > CONTEXT_ATTACHMENT_MAX_BYTES) {\n errors.push({ code: 'invalid-envelope', path, message: 'attachment envelope is invalid' })\n return\n }\n const attachmentId = candidate.attachment_id as string\n if (ids.has(attachmentId))\n errors.push({ attachment_id: attachmentId, code: 'duplicate-attachment-id', path: `${path}.attachment_id`, message: 'attachment_id must be unique within the message' })\n ids.add(attachmentId)\n const attachmentBytes = utf8ByteLength(JSON.stringify(candidate))\n const knownV2 = candidate.kind === ATTENTION_CONTEXT_KIND && candidate.version === ATTENTION_CONTEXT_V2_VERSION\n const knownV3 = candidate.kind === ATTENTION_CONTEXT_KIND && candidate.version === ATTENTION_CONTEXT_V3_VERSION\n const knownV4 = candidate.kind === ATTENTION_CONTEXT_KIND && candidate.version === ATTENTION_CONTEXT_V4_VERSION\n const limit = knownV2 || knownV3 || knownV4\n ? Math.min(maxAttachmentBytes, knownV4 ? ATTENTION_V3_MAX_ENVELOPE_BYTES : knownV3 ? ATTENTION_V3_MAX_ENVELOPE_BYTES : ATTENTION_V2_MAX_ENVELOPE_BYTES)\n : maxAttachmentBytes\n if (attachmentBytes > limit)\n errors.push({ attachment_id: attachmentId, code: 'attachment-bytes-exceeded', path, message: `attachment exceeds ${limit} bytes` })\n const contentBytes = utf8ByteLength(candidate.content as string)\n if (contentBytes !== candidate.content_bytes)\n errors.push({ attachment_id: attachmentId, code: 'content-size-mismatch', path: `${path}.content_bytes`, message: 'content_bytes does not match the canonical payload' })\n if (!/^sha256:[a-f0-9]{64}$/.test(candidate.content_hash as string))\n errors.push({ attachment_id: attachmentId, code: 'invalid-content-hash', path: `${path}.content_hash`, message: 'content_hash must be a lowercase SHA-256 digest' })\n if (!options.allowExpired && candidate.expires_at && Date.parse(candidate.expires_at as string) <= (options.now ?? Date.now()))\n errors.push({ attachment_id: attachmentId, code: 'expired', path: `${path}.expires_at`, message: 'attachment has expired' })\n let payload: unknown\n try {\n payload = JSON.parse(candidate.content as string)\n assertJsonValue(payload)\n }\n catch {\n errors.push({ attachment_id: attachmentId, code: 'invalid-envelope', path: `${path}.content`, message: 'content must be valid bounded JSON' })\n return\n }\n if (canonicalJson(payload as JsonValue) !== candidate.content) {\n errors.push({ attachment_id: attachmentId, code: 'noncanonical-content', path: `${path}.content`, message: 'content must use canonical JSON encoding' })\n return\n }\n const isVisualV1 = candidate.kind === ATTENTION_VISUAL_CONTEXT_KIND && candidate.version === ATTENTION_VISUAL_CONTEXT_VERSION\n if (containsForbiddenContextKey(payload, isVisualV1)) {\n errors.push({ attachment_id: attachmentId, code: 'forbidden-live-field', path: `${path}.content`, message: 'durable context must not contain live capability or bearer fields' })\n return\n }\n if (candidate.kind === ATTENTION_CONTEXT_KIND && candidate.version === ATTENTION_CONTEXT_VERSION) {\n if (!isAttentionSnapshotV1(payload))\n errors.push({ attachment_id: attachmentId, code: 'invalid-attention-payload', path: `${path}.content`, message: 'wippy.attention v1 content is invalid or contains a forbidden live capability field' })\n expandedAttentionBytes += contentBytes\n }\n if (knownV2) {\n try {\n const expanded = decodeAttentionSnapshotV2(payload, ATTENTION_MAX_EXPANDED_BYTES - expandedAttentionBytes)\n expandedAttentionBytes += utf8ByteLength(compactJson(expanded))\n }\n catch {\n errors.push({ attachment_id: attachmentId, code: 'invalid-attention-payload', path: `${path}.content`, message: 'wippy.attention v2 content is invalid or exceeds expansion budgets' })\n }\n }\n if (knownV3) {\n try {\n const expanded = decodeAttentionSnapshotV3(payload, ATTENTION_MAX_EXPANDED_BYTES - expandedAttentionBytes)\n expandedAttentionBytes += utf8ByteLength(compactJson(expanded))\n }\n catch {\n errors.push({ attachment_id: attachmentId, code: 'invalid-attention-payload', path: `${path}.content`, message: 'wippy.attention v3 content is invalid or exceeds expansion budgets' })\n }\n }\n if (knownV4) {\n try {\n const expanded = decodeAttentionSnapshotV4(payload, ATTENTION_MAX_EXPANDED_BYTES - expandedAttentionBytes)\n expandedAttentionBytes += utf8ByteLength(compactJson(expanded))\n }\n catch {\n errors.push({ attachment_id: attachmentId, code: 'invalid-attention-payload', path: `${path}.content`, message: 'wippy.attention v4 content is invalid or exceeds expansion budgets' })\n }\n }\n if (expandedAttentionBytes > ATTENTION_MAX_EXPANDED_BYTES)\n errors.push({ attachment_id: attachmentId, code: 'invalid-attention-payload', path: `${path}.content`, message: 'Attention expansion exceeds the aggregate byte budget' })\n if (isVisualV1) {\n if (!isAttentionVisualContextV1(payload) || candidate.expires_at !== payload.expires_at)\n errors.push({ attachment_id: attachmentId, code: 'invalid-attention-payload', path: `${path}.content`, message: 'wippy.attention.visual v1 content is invalid or contains a forbidden live capability field' })\n }\n })\n if (errors.length > 0)\n return { ok: false, errors }\n return { ok: true, attachments: value as ContextAttachmentEnvelope[] }\n}\n\nexport async function validateContextAttachmentsIntegrity(\n value: unknown,\n options: ContextAttachmentValidationOptions = {},\n): Promise<ContextAttachmentValidationResult> {\n const structural = validateContextAttachments(value, options)\n if (!structural.ok)\n return structural\n\n const errors: ContextAttachmentValidationError[] = []\n for (let index = 0; index < structural.attachments.length; index++) {\n const attachment = structural.attachments[index]\n if (await sha256Text(attachment.content) !== attachment.content_hash) {\n errors.push({\n attachment_id: attachment.attachment_id,\n code: 'content-hash-mismatch',\n path: `context_attachments[${index}].content_hash`,\n message: 'content_hash does not match the canonical payload',\n })\n }\n if (attachment.kind === ATTENTION_CONTEXT_KIND && (attachment.version === ATTENTION_CONTEXT_VERSION\n || attachment.version === ATTENTION_CONTEXT_V2_VERSION\n || attachment.version === ATTENTION_CONTEXT_V3_VERSION\n || attachment.version === ATTENTION_CONTEXT_V4_VERSION)) {\n const payload = JSON.parse(attachment.content)\n const snapshot = attachment.version === ATTENTION_CONTEXT_V2_VERSION\n ? decodeAttentionSnapshotV2(payload)\n : attachment.version === ATTENTION_CONTEXT_V3_VERSION\n ? decodeAttentionSnapshotV3(payload)\n : attachment.version === ATTENTION_CONTEXT_V4_VERSION\n ? decodeAttentionSnapshotV4(payload)\n : payload as AttentionSnapshotV1\n for (let candidateIndex = 0; candidateIndex < snapshot.candidates.length; candidateIndex++) {\n const candidate = snapshot.candidates[candidateIndex]\n if (candidate.action_ref && !await attentionPathDigestMatches(candidate.path, candidate.action_ref.path_digest)) {\n errors.push({\n attachment_id: attachment.attachment_id,\n code: 'action-ref-path-digest-mismatch',\n path: `context_attachments[${index}].content.candidates[${candidateIndex}].action_ref.path_digest`,\n message: 'action_ref.path_digest does not match the canonical candidate path',\n })\n }\n }\n }\n }\n\n if (errors.length > 0)\n return { ok: false, errors }\n return structural\n}\n\nexport function createAttentionSampleGrid(origin: AttentionPoint, radiusCssPx = 20, stepCssPx = 5): AttentionQueryPoint[] {\n if (!isPoint(origin))\n throw new RangeError('origin must contain finite CSS-pixel coordinates')\n if (!Number.isFinite(radiusCssPx) || radiusCssPx < 0 || radiusCssPx > 100)\n throw new RangeError('radiusCssPx must be a finite number between 0 and 100')\n if (!Number.isFinite(stepCssPx) || stepCssPx < 1 || stepCssPx > 100)\n throw new RangeError('stepCssPx must be a finite number between 1 and 100')\n const extent = Math.floor(radiusCssPx / stepCssPx)\n const squarePointCount = (extent * 2 + 1) ** 2\n if (squarePointCount > 4096)\n throw new RangeError('sampling grid exceeds the maximum of 4096 points')\n const points: AttentionQueryPoint[] = []\n for (let yIndex = -extent; yIndex <= extent; yIndex++) {\n const y = yIndex * stepCssPx\n for (let xIndex = -extent; xIndex <= extent; xIndex++) {\n const x = xIndex * stepCssPx\n if (x * x + y * y <= radiusCssPx * radiusCssPx) {\n points.push({\n point_id: `p${points.length}`,\n x: origin.x + x,\n y: origin.y + y,\n })\n }\n }\n }\n return points\n}\n","import type { AttentionFocus, AttentionFocusState, AttentionPathSegment, AttentionRealmKind, AttentionRect, AttentionSelectionState, AttentionTargetSummary } from './attention'\nimport { isAttentionFocus, isAttentionFocusState, isAttentionObservedEvent, isAttentionSelection, isAttentionSelectionState, isAttentionStructuralPath, utf8ByteLength } from './attention'\n\nexport interface AttentionNodeRef {\n host_instance_id: string\n node_id: string\n mount_id: string\n generation: number\n}\n\nexport interface AttentionLayoutPanel {\n id: string\n element?: Element | null\n window?: Window | null\n role?: string\n title?: string\n active: boolean\n collapsed: boolean\n drawer: boolean\n floating: boolean\n modal: boolean\n placeholder: boolean\n}\nexport interface AttentionLayoutRegistration {\n instance_id?: string\n root: Element\n panels: AttentionLayoutPanel[]\n breakpoint?: string\n}\nexport interface AttentionLayoutHandle {\n instance_id: string\n update: (state: Omit<AttentionLayoutRegistration, 'instance_id' | 'root'>) => void\n dispose: () => void\n}\n\nexport interface AttentionInspectionRevisions {\n tree: number\n observation: number\n geometry: number\n}\n\nexport interface AttentionNodeGeometry {\n rect: AttentionRect | null\n clip: AttentionRect | null\n visible: boolean\n offscreen: boolean\n quality: 'exact' | 'approximate' | 'unavailable'\n coordinate_space: 'host-viewport' | { mount_id: string, generation: number }\n}\n\nexport interface AttentionInspectionNode {\n ref: AttentionNodeRef\n parent: AttentionNodeRef | null\n kind: AttentionRealmKind | 'document' | 'semantic' | 'placeholder'\n path: AttentionPathSegment[]\n summary: AttentionTargetSummary\n resource?: { resource_id?: string, package_id?: string, package_version?: string, tag_name?: string, title?: string }\n layout?: { instance_id: string, panel_id: string, role?: string, title?: string, breakpoint?: string, active: boolean, collapsed: boolean, drawer: boolean, floating: boolean, modal: boolean }\n geometry?: AttentionNodeGeometry\n state: 'mounted' | 'hidden' | 'unmounted' | 'unavailable'\n}\n\nexport type AttentionInspectionReason = 'excluded' | 'detached' | 'navigation' | 'disconnected' | 'unauthorized'\n | 'unsupported-version' | 'unsupported-transform' | 'deadline-exceeded'\n | 'node-limit' | 'depth-limit' | 'byte-limit' | 'work-limit' | 'registry-limit'\n | 'revision-changed' | 'provider-unavailable' | 'invalid-request' | 'backpressure'\n\nexport interface AttentionLocalTree {\n root: AttentionNodeRef | null\n nodes: AttentionInspectionNode[]\n revisions: AttentionInspectionRevisions\n omissions: Array<{ node?: AttentionNodeRef, reason: AttentionInspectionReason }>\n complete: boolean\n examined: number\n next_offset?: number\n}\n\nexport type AttentionInspectionOperation = 'cursor' | 'focus' | 'selection' | 'point' | 'tree' | 'find' | 'geometry' | 'subscribe' | 'unsubscribe' | 'change'\nexport type AttentionChangeKind = 'cursor' | 'focus' | 'selection' | 'tree' | 'geometry' | 'invalidation'\nexport interface AttentionNotification {\n subscription_id: string\n sequence: number\n revisions: AttentionInspectionRevisions\n kind: AttentionChangeKind\n nodes: AttentionNodeRef[]\n dropped: number\n reason?: 'backpressure' | 'disconnected' | 'detached'\n closed?: boolean\n}\nexport interface AttentionSubscriptionOptions extends AttentionInspectionScope { events: AttentionChangeKind[] }\nexport interface AttentionSubscription { dispose: () => void }\nexport type AttentionInspectionOutcome = 'ok' | 'empty' | 'cleared' | 'unknown' | 'partial' | 'unavailable' | 'stale' | 'cancelled'\nexport interface AttentionInspectionScope { fromRoot?: boolean, node?: AttentionNodeRef }\nexport type AttentionSearch = { node_id: string } | { role?: string, name?: string, text?: string, resource_id?: string } | { css: string, scope: AttentionNodeRef }\nexport interface AttentionInspectionResult {\n request_id: string\n host_instance_id: string\n measured_at: string\n revisions: AttentionInspectionRevisions\n outcome: AttentionInspectionOutcome\n data: Record<string, unknown> | null\n omissions: Array<{ node?: AttentionNodeRef, reason: AttentionInspectionReason }>\n continuation?: string\n}\nexport interface AttentionInspectionCall {\n operation: AttentionInspectionOperation\n scope: AttentionInspectionScope\n args: Record<string, unknown>\n}\n\nexport function isAttentionNodeRef(value: unknown): value is AttentionNodeRef {\n if (!value || typeof value !== 'object' || Array.isArray(value))\n return false\n const ref = value as Record<string, unknown>\n return Object.keys(ref).length === 4\n && ['host_instance_id', 'node_id', 'mount_id'].every(key => typeof ref[key] === 'string' && utf8ByteLength(ref[key]) > 0 && utf8ByteLength(ref[key]) <= 160)\n && Number.isSafeInteger(ref.generation) && Number(ref.generation) > 0\n}\n\nexport interface AttentionInspectionMount { mount_id: string, generation: number }\nexport interface AttentionInspectionObservation {\n focus: AttentionFocus | null\n focus_state?: AttentionFocusState\n selection_state: AttentionSelectionState\n anchor: AttentionNodeRef | null\n focus_node: AttentionNodeRef | null\n}\nexport function isAttentionInspectionObservation(value: unknown): value is AttentionInspectionObservation {\n return object(value)\n && keys(value, ['focus', 'focus_state', 'selection_state', 'anchor', 'focus_node'])\n && (value.focus === null || isAttentionFocus(value.focus))\n && (value.focus_state === undefined || isAttentionFocusState(value.focus_state))\n && isAttentionSelectionState(value.selection_state)\n && (value.anchor === null || isAttentionNodeRef(value.anchor))\n && (value.focus_node === null || isAttentionNodeRef(value.focus_node))\n}\nexport interface AttentionInspectionRequest extends AttentionInspectionCall {\n schema: 'wippy.attention.relay.v2'\n message_type: 'inspect-request'\n request_id: string\n host_instance_id: string\n source_mount: AttentionInspectionMount\n target_mount: AttentionInspectionMount\n origin_mount: AttentionInspectionMount\n capability_token: string\n sequence: number\n created_at: string\n deadline_at: string\n budget: { nodes: number, depth: number, queries: number, work: number, bytes: number }\n}\nexport interface AttentionInspectionResponse extends AttentionInspectionResult {\n schema: 'wippy.attention.relay.v2'\n message_type: 'inspect-result'\n source_mount: AttentionInspectionMount\n sequence: number\n}\nexport interface AttentionInspectionCancel {\n schema: 'wippy.attention.relay.v2'\n message_type: 'inspect-cancel'\n request_id: string\n host_instance_id: string\n source_mount: AttentionInspectionMount\n target_mount: AttentionInspectionMount\n capability_token: string\n sequence: number\n}\nexport type AttentionInspectionMessage = AttentionInspectionRequest | AttentionInspectionResponse | AttentionInspectionCancel\n\nfunction object(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\nfunction text(value: unknown, max = 256): value is string {\n return typeof value === 'string' && value.length > 0 && value.length <= max\n}\nfunction keys(value: Record<string, unknown>, allowed: string[]): boolean {\n return Object.keys(value).every(key => allowed.includes(key))\n}\nfunction integer(value: unknown, minimum = 0, maximum = Number.MAX_SAFE_INTEGER): boolean {\n return Number.isSafeInteger(value) && Number(value) >= minimum && Number(value) <= maximum\n}\nfunction wireBytes(value: unknown): number {\n try {\n return utf8ByteLength(JSON.stringify(value))\n }\n catch {\n return Number.POSITIVE_INFINITY\n }\n}\nfunction mount(value: unknown): value is AttentionInspectionMount {\n return object(value) && Object.keys(value).length === 2 && text(value.mount_id, 160) && utf8ByteLength(value.mount_id) <= 160 && integer(value.generation, 1)\n}\nfunction revisions(value: unknown): boolean {\n return object(value) && Object.keys(value).length === 3 && ['tree', 'observation', 'geometry'].every(key => integer(value[key]))\n}\n\nexport function isAttentionChangeKind(value: unknown): value is AttentionChangeKind {\n return ['cursor', 'focus', 'selection', 'tree', 'geometry', 'invalidation'].includes(String(value))\n}\n\nexport function isAttentionNotification(value: unknown): value is AttentionNotification {\n return object(value) && keys(value, ['subscription_id', 'sequence', 'revisions', 'kind', 'nodes', 'dropped', 'reason', 'closed'])\n && text(value.subscription_id) && integer(value.sequence, 1) && revisions(value.revisions)\n && isAttentionChangeKind(value.kind) && integer(value.dropped)\n && Array.isArray(value.nodes) && value.nodes.length <= 128 && value.nodes.every(isAttentionNodeRef)\n && (value.reason === undefined || ['backpressure', 'disconnected', 'detached'].includes(String(value.reason)))\n && (value.closed === undefined || typeof value.closed === 'boolean')\n && wireBytes(value) <= 16_384\n}\n\nfunction boundedFields(value: unknown, allowed: string[], maximum = 1024): value is Record<string, string> {\n return object(value) && keys(value, allowed)\n && Object.values(value).every(member => typeof member === 'string' && utf8ByteLength(member) <= maximum)\n}\n\nfunction rect(value: unknown): boolean {\n return object(value) && Object.keys(value).length === 4 && keys(value, ['x', 'y', 'width', 'height'])\n && Object.values(value).every(member => typeof member === 'number' && Number.isFinite(member))\n && Number(value.width) >= 0 && Number(value.height) >= 0\n}\n\nfunction geometry(value: unknown): boolean {\n return object(value) && Object.keys(value).length === 6 && keys(value, ['rect', 'clip', 'visible', 'offscreen', 'quality', 'coordinate_space'])\n && (value.rect === null || rect(value.rect)) && (value.clip === null || rect(value.clip))\n && typeof value.visible === 'boolean' && typeof value.offscreen === 'boolean'\n && ['exact', 'approximate', 'unavailable'].includes(String(value.quality))\n && (value.coordinate_space === 'host-viewport' || mount(value.coordinate_space))\n && (value.quality !== 'unavailable' || (value.rect === null && value.clip === null))\n}\n\nfunction inspectionSummary(value: unknown): boolean {\n if (!object(value) || !keys(value, ['role', 'name', 'text', 'value', 'state']))\n return false\n const { state, ...labels } = value\n return boundedFields(labels, ['role', 'name', 'text', 'value'])\n && (state === undefined || (object(state) && Object.keys(state).length <= 16\n && Object.entries(state).every(([key, member]) => utf8ByteLength(key) <= 64\n && (member === null || typeof member === 'boolean' || (typeof member === 'number' && Number.isFinite(member))\n || (typeof member === 'string' && utf8ByteLength(member) <= 256)))))\n}\n\nexport function isAttentionInspectionNode(value: unknown): value is AttentionInspectionNode {\n if (!object(value) || !keys(value, ['ref', 'parent', 'kind', 'path', 'summary', 'resource', 'layout', 'geometry', 'state'])\n || !isAttentionNodeRef(value.ref) || (value.parent !== null && !isAttentionNodeRef(value.parent))\n || !['host', 'panel', 'artifact', 'page', 'iframe', 'web-fragment', 'web-component', 'shadow-root', 'element', 'document', 'semantic', 'placeholder'].includes(String(value.kind))\n || !['mounted', 'hidden', 'unmounted', 'unavailable'].includes(String(value.state))\n || !isAttentionStructuralPath(value.path) || !object(value.summary)\n || !keys(value.summary, ['role', 'name', 'text', 'value', 'state'])) {\n return false\n }\n if (!inspectionSummary(value.summary) || (value.parent !== null && value.parent.host_instance_id !== value.ref.host_instance_id)) {\n return false\n }\n if (value.resource !== undefined && !boundedFields(value.resource, ['resource_id', 'package_id', 'package_version', 'tag_name', 'title']))\n return false\n if (value.layout !== undefined) {\n const layout = value.layout\n const flags = ['active', 'collapsed', 'drawer', 'floating', 'modal']\n if (!object(layout) || !keys(layout, ['instance_id', 'panel_id', 'role', 'title', 'breakpoint', ...flags])\n || !text(layout.instance_id) || !text(layout.panel_id)\n || flags.some(key => typeof layout[key] !== 'boolean')\n || ['role', 'title', 'breakpoint'].some(key => layout[key] !== undefined && (!text(layout[key]) || utf8ByteLength(String(layout[key])) > 256))) {\n return false\n }\n }\n return value.geometry === undefined || geometry(value.geometry)\n}\n\nfunction inspectionData(value: unknown, host: string): boolean {\n if (value === null)\n return true\n if (!object(value))\n return false\n const ref = (member: unknown) => isAttentionNodeRef(member) && member.host_instance_id === host\n const node = (member: unknown) => isAttentionInspectionNode(member) && member.ref.host_instance_id === host\n if ('notification' in value)\n return keys(value, ['notification']) && isAttentionNotification(value.notification) && value.notification.nodes.every(ref)\n if ('subscription_id' in value)\n return keys(value, ['subscription_id']) && text(value.subscription_id, 160)\n if ('nodes' in value) {\n if (!keys(value, ['nodes', 'root', 'event', 'point', 'observation']) || !Array.isArray(value.nodes)\n || value.nodes.length > 256 || !value.nodes.every(node)) {\n return false\n }\n return (value.root === undefined || value.root === null || ref(value.root))\n && (value.event === undefined || value.event === null || isAttentionObservedEvent(value.event))\n && (value.point === undefined || (object(value.point) && Object.keys(value.point).length === 2 && keys(value.point, ['x', 'y'])\n && typeof value.point.x === 'number' && Number.isFinite(value.point.x) && typeof value.point.y === 'number' && Number.isFinite(value.point.y)))\n && (value.observation === undefined || (isAttentionInspectionObservation(value.observation)\n && (!value.observation.focus || inspectionSummary(value.observation.focus.summary))\n && (value.observation.anchor === null || ref(value.observation.anchor))\n && (value.observation.focus_node === null || ref(value.observation.focus_node))))\n }\n if ('selection' in value) {\n return Object.keys(value).length === 3 && keys(value, ['selection', 'anchor', 'focus'])\n && (value.selection === null || isAttentionSelection(value.selection))\n && (value.anchor === null || ref(value.anchor)) && (value.focus === null || ref(value.focus))\n }\n if ('focus' in value) {\n return Object.keys(value).length === 2 && keys(value, ['focus', 'node'])\n && (value.focus === null || (isAttentionFocus(value.focus) && inspectionSummary(value.focus.summary)))\n && (value.node === null || node(value.node))\n }\n if ('node' in value) {\n const { node: geometryNode, ...metrics } = value\n return ref(geometryNode) && geometry(metrics)\n }\n return false\n}\n\n/** The shared v2 envelope is validated after source/origin authentication. */\nexport function isAttentionInspectionMessage(value: unknown): value is AttentionInspectionMessage {\n if (!object(value) || value.schema !== 'wippy.attention.relay.v2' || !text(value.request_id) || !text(value.host_instance_id)\n || !mount(value.source_mount) || !integer(value.sequence) || wireBytes(value) > 262_144) {\n return false\n }\n const common = ['schema', 'message_type', 'request_id', 'host_instance_id', 'source_mount', 'sequence']\n if (value.message_type === 'inspect-result') {\n if (!keys(value, [...common, 'measured_at', 'revisions', 'outcome', 'data', 'omissions', 'continuation'])\n || !text(value.measured_at, 64) || !Number.isFinite(Date.parse(value.measured_at)) || !revisions(value.revisions)\n || !['ok', 'empty', 'cleared', 'unknown', 'partial', 'unavailable', 'stale', 'cancelled'].includes(String(value.outcome))\n || !Array.isArray(value.omissions) || value.omissions.length > 256\n || value.omissions.some(item => !object(item) || !keys(item, ['node', 'reason']) || !text(item.reason, 64) || (item.node !== undefined && !isAttentionNodeRef(item.node)))\n || (value.continuation !== undefined && !text(value.continuation))) {\n return false\n }\n return inspectionData(value.data, value.host_instance_id)\n }\n if (!mount(value.target_mount) || !text(value.capability_token, 512))\n return false\n if (value.message_type === 'inspect-cancel')\n return keys(value, [...common, 'target_mount', 'capability_token'])\n if (value.message_type !== 'inspect-request' || !keys(value, [...common, 'target_mount', 'origin_mount', 'capability_token', 'created_at', 'deadline_at', 'operation', 'scope', 'args', 'budget'])\n || !mount(value.origin_mount) || !text(value.created_at, 64) || !text(value.deadline_at, 64)\n || !Number.isFinite(Date.parse(value.created_at)) || !Number.isFinite(Date.parse(value.deadline_at))\n || Date.parse(value.deadline_at) - Date.parse(value.created_at) > 1500\n || Date.parse(value.deadline_at) <= Date.parse(value.created_at)\n || !['cursor', 'focus', 'selection', 'point', 'tree', 'find', 'geometry', 'subscribe', 'unsubscribe', 'change'].includes(String(value.operation))\n || !object(value.scope) || !keys(value.scope, ['fromRoot', 'node'])\n || (value.scope.fromRoot !== undefined && typeof value.scope.fromRoot !== 'boolean')\n || (value.scope.node !== undefined && !isAttentionNodeRef(value.scope.node)) || !object(value.args)\n || !object(value.budget) || Object.keys(value.budget).length !== 5) {\n return false\n }\n return integer(value.budget.nodes, 1, 256) && integer(value.budget.depth, 0, 32)\n && integer(value.budget.queries, 1, 128) && integer(value.budget.work, 0, 8192) && integer(value.budget.bytes, 1, 262_144)\n}\n","import type { AttentionPathSegment } from './attention'\nimport { ATTENTION_RELAY_SCHEMA, canonicalJson, isAttentionRelayMessageV1, isAttentionStructuralPath, utf8ByteLength } from './attention'\nimport { isAttentionInspectionMessage } from './attention-inspection'\n\nexport const ATTENTION_RELAY_SCHEMA_V2 = 'wippy.attention.relay.v2'\nconst MAX_WIRE_BYTES = 1_048_576\nconst MAX_EXPANDED_BYTES = 2 * MAX_WIRE_BYTES\nconst MAX_DICTIONARY_SEGMENTS = 8192\nconst PATH_KEYS = new Set(['path', 'anchor_path', 'focus_path'])\nconst receivedBytes = new WeakMap<object, number>()\n\nfunction record(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n\n/** Geometry and every structural segment are retained; only repeated segments are interned. */\nexport function encodeAttentionRelayMessage(value: unknown, version: 1 | 2 = 1): unknown {\n if (version === 1 || !record(value) || value.schema !== ATTENTION_RELAY_SCHEMA)\n return value\n const dictionary: AttentionPathSegment[] = []\n const indices = new Map<string, number>()\n const encode = (item: unknown, key = ''): unknown => {\n if (PATH_KEYS.has(key)) {\n if (!isAttentionStructuralPath(item))\n throw new TypeError('Invalid Attention relay path')\n return item.map((segment) => {\n const canonical = canonicalJson(JSON.parse(JSON.stringify(segment)))\n let index = indices.get(canonical)\n if (index === undefined) {\n index = dictionary.length\n if (index >= MAX_DICTIONARY_SEGMENTS)\n throw new RangeError('Attention relay path dictionary limit exceeded')\n indices.set(canonical, index)\n dictionary.push(segment)\n }\n return index\n })\n }\n if (Array.isArray(item))\n return item.map(entry => encode(entry))\n if (record(item))\n return Object.fromEntries(Object.entries(item).map(([name, entry]) => [name, encode(entry, name)]))\n return item\n }\n return { ...encode(value) as Record<string, unknown>, schema: ATTENTION_RELAY_SCHEMA_V2, path_dictionary: dictionary }\n}\n\n/** Decode before semantic validation, but only after the caller checks source and origin. */\nexport function decodeAttentionRelayMessage(value: unknown): unknown {\n if (!record(value) || value.schema !== ATTENTION_RELAY_SCHEMA_V2)\n return value\n if (isAttentionInspectionMessage(value))\n return value\n const wireBytes = utf8ByteLength(JSON.stringify(value))\n if (wireBytes > MAX_WIRE_BYTES || !Array.isArray(value.path_dictionary)\n || value.path_dictionary.length > MAX_DICTIONARY_SEGMENTS\n || value.path_dictionary.some(segment => !isAttentionStructuralPath([segment]))) {\n return undefined\n }\n const dictionary = value.path_dictionary as AttentionPathSegment[]\n let visits = 0\n const decode = (item: unknown, key = '', depth = 0): unknown => {\n if (++visits > 65_536 || depth > 32)\n throw new RangeError('Attention relay expansion limit exceeded')\n if (PATH_KEYS.has(key)) {\n if (!Array.isArray(item) || item.length === 0 || item.length > 32\n || item.some(index => !Number.isSafeInteger(index) || index < 0 || index >= dictionary.length)) {\n throw new TypeError('Invalid Attention relay path reference')\n }\n return item.map(index => ({ ...dictionary[index] }))\n }\n if (Array.isArray(item))\n return item.map(entry => decode(entry, '', depth + 1))\n if (record(item))\n return Object.fromEntries(Object.entries(item).map(([name, entry]) => [name, decode(entry, name, depth + 1)]))\n return item\n }\n try {\n const { path_dictionary: _dictionary, ...envelope } = value\n const expanded = { ...decode(envelope) as Record<string, unknown>, schema: ATTENTION_RELAY_SCHEMA }\n if (utf8ByteLength(JSON.stringify(expanded)) > MAX_EXPANDED_BYTES || !isAttentionRelayMessageV1(expanded))\n return undefined\n receivedBytes.set(expanded, wireBytes)\n return expanded\n }\n catch {\n return undefined\n }\n}\n\nexport function attentionRelayByteLength(value: unknown, version?: 1 | 2): number {\n if (version === undefined && record(value)) {\n const actual = receivedBytes.get(value)\n if (actual !== undefined)\n return actual\n }\n return utf8ByteLength(JSON.stringify(version === undefined ? value : encodeAttentionRelayMessage(value, version)))\n}\n","/**\n * Surface portability contract, version 1 (EE2-2339).\n *\n * A **surface** is the page-owned rectangular rendering area the host\n * allocates. The same page package must lay out correctly whether the host\n * renders it as a srcdoc iframe or as a Web Fragment, so the contract names\n * one observable structure both engines materialize:\n *\n * - a query container named `wippy-surface` whose content box IS the\n * allocated surface, and which is a flat-tree ancestor of all page content;\n * - four reserved custom properties pinned to that container.\n *\n * The two engines place that container in different documents — the iframe\n * engine injects it into the srcdoc, the fragment engine uses a host-document\n * wrapper above the reframed shadow roots — but authors see one contract.\n *\n * **Why a wrapper and not `html` / `body`** (measured, Chrome 148):\n * `container-type: size` on the document root yields no block axis at all\n * (`min-height` queries never match, `cqh` resolves to `0px`), and `body`\n * redefines the inline axis through its own margin/padding/border. Only a\n * dedicated element carries both axes faithfully.\n */\n\n/**\n * How the host sizes the surface.\n *\n * - `container` — the host imposes both dimensions. Both axes are queryable.\n * - `content` — the host imposes the inline axis and the page's intrinsic\n * content determines the block axis. Only the inline axis is queryable;\n * exposing a measured block size here would create a resize feedback loop.\n *\n * `auto-height` on a page/artifact is the compatibility spelling of `content`.\n */\nexport type PageSizing = 'container' | 'content'\n\n/**\n * Which engine materialized this surface.\n *\n * `host` is not a page engine: it is a component mounted directly into the\n * host document (or the standalone dev proxy), where no surface is allocated\n * and no query box exists. Such a context reports zero width and `content`\n * sizing — deliberately, rather than borrowing the host viewport, which would\n * be the false viewport equivalence the contract exists to avoid.\n */\nexport type SurfaceEngine = 'iframe' | 'fragment' | 'host'\n\n/**\n * Capabilities a page can require via `wippy.surface.requirements`, or test at\n * runtime via `host.surface.supports()`.\n *\n * The inline axis is the contract baseline and deliberately has no token — it\n * is available on every surface, so requiring it would say nothing.\n *\n * - `block-size` — block-axis queries, `cqb`, aspect-ratio and orientation.\n * Container sizing only.\n * - `surface-scroll` — a page-owned scrollport. Container sizing only.\n * - `registered-hit-testing` / `native-document-hit-testing` — deep hit\n * testing through shadow roots.\n * - `owner-visibility` — the owner-visible field on intersection records.\n *\n * The last three are declared so the vocabulary is stable, but are not\n * implemented in this release: `supports()` reports them false and a package\n * requiring one is rejected before startup rather than rendering degraded.\n */\nexport type SurfaceCapability\n = | 'block-size'\n | 'surface-scroll'\n | 'registered-hit-testing'\n | 'native-document-hit-testing'\n | 'owner-visibility'\n\n/** Every capability token, in declaration order. */\nexport const SURFACE_CAPABILITIES: readonly SurfaceCapability[] = [\n 'block-size',\n 'surface-scroll',\n 'registered-hit-testing',\n 'native-document-hit-testing',\n 'owner-visibility',\n]\n\n/**\n * Capabilities this release can actually deliver, and the sizing each needs.\n *\n * A capability absent from this map is part of the vocabulary but unshipped.\n */\nexport const SURFACE_SUPPORTED_CAPABILITIES: Partial<Record<SurfaceCapability, PageSizing>> = {\n 'block-size': 'container',\n 'surface-scroll': 'container',\n}\n\n/** The only contract version this host implements. */\nexport const SURFACE_CONTRACT_VERSION = 1 as const\n\n/** Reserved query-container name. Applications must not override it. */\nexport const SURFACE_CONTAINER_NAME = 'wippy-surface' as const\n\n/**\n * The four reserved custom properties, pinned to the surface container.\n *\n * Applications and their bundled dependencies must not declare, register\n * (`@property` / `CSS.registerProperty()`), or assign these anywhere — a\n * descendant declaration shadows inheritance and silently unpins the value\n * from the surface.\n *\n * Values are literal CSS-pixel lengths, never `cq*` token streams: native\n * container units can retarget to a nearer eligible container, and a missing\n * axis can fall back to the small viewport.\n */\nexport const SURFACE_WIDTH_PROPERTY = '--wippy-surface-width' as const\nexport const SURFACE_WIDTH_UNIT_PROPERTY = '--wippy-surface-width-unit' as const\nexport const SURFACE_HEIGHT_PROPERTY = '--wippy-surface-height' as const\nexport const SURFACE_HEIGHT_UNIT_PROPERTY = '--wippy-surface-height-unit' as const\n\n/**\n * Immutable geometry snapshot.\n *\n * `height` and `heightUnit` are `null` in content sizing — the block axis is\n * genuinely unavailable there, and a number would imply a viewport\n * equivalence that does not exist.\n */\nexport interface SurfaceSnapshot {\n /** Contract version that produced this snapshot. */\n readonly contract: typeof SURFACE_CONTRACT_VERSION\n /** Monotonic, bumped once per coalesced measurement. */\n readonly revision: number\n readonly engine: SurfaceEngine\n readonly sizing: PageSizing\n /** Physical content-box width in CSS pixels. */\n readonly width: number\n /** 1% of `width`, in CSS pixels. */\n readonly widthUnit: number\n /** Physical content-box height in CSS pixels; `null` in content sizing. */\n readonly height: number | null\n /** 1% of `height`, in CSS pixels; `null` in content sizing. */\n readonly heightUnit: number | null\n}\n\n/** Read API exposed to pages as `host.surface`. */\nexport interface SurfaceApi {\n readonly contract: typeof SURFACE_CONTRACT_VERSION\n /** Which engine rendered this page. Prefer `supports()` for behavior decisions. */\n readonly engine: SurfaceEngine\n readonly sizing: PageSizing\n /**\n * Current geometry, re-read from computed style on **every** access — never\n * memoized, so a value read after a layout change is never frame-stale.\n *\n * That also means it forces a style read each time. Cheap for the occasional\n * read; do not poll it in your own animation frame. Use `onChange` to be told\n * when it moves, and read once per notification.\n */\n readonly snapshot: SurfaceSnapshot\n /** Subscribe to geometry changes. Returns an idempotent unsubscribe. */\n onChange: (listener: (snapshot: SurfaceSnapshot) => void) => () => void\n /** Is a capability available for this surface? */\n supports: (capability: SurfaceCapability) => boolean\n}\n\n/**\n * `wippy.surface` in a page package's `package.json`.\n *\n * Absent means the package predates the contract: it still renders, but the\n * host does not advertise portability for it and declares no requirements.\n */\nexport interface SurfaceManifest {\n contract: typeof SURFACE_CONTRACT_VERSION\n /**\n * Capabilities the page cannot render without. The spelling is\n * `requirements` — matching the normative manifest format. An empty or\n * absent list means the page needs only the baseline inline axis.\n */\n requirements?: SurfaceCapability[]\n}\n\n/**\n * Can this surface deliver `capability`?\n *\n * False for tokens that are part of the vocabulary but unshipped, so a page\n * branching on `supports()` degrades instead of assuming a behavior that is\n * not there.\n */\nexport function sizingSupports(sizing: PageSizing, capability: SurfaceCapability): boolean {\n const required = SURFACE_SUPPORTED_CAPABILITIES[capability]\n return required !== undefined && required === sizing\n}\n","/**\n * Host-owned logical activity for a retained web component.\n *\n * This is deliberately independent from CSS visibility, viewport\n * intersection, and document visibility. Hosts set it before connection and\n * update it without replacing the element when a retained surface changes\n * ownership.\n */\nexport const WIPPY_VISIBILITY_ATTRIBUTE = 'data-wippy-visible'\n\n/** Parse the public attribute. Absent or invalid values are unmanaged. */\nexport function readWippyVisibility(element: Element): boolean | null {\n const value = element.getAttribute(WIPPY_VISIBILITY_ATTRIBUTE)\n if (value === 'true')\n return true\n if (value === 'false')\n return false\n return null\n}\n\n/**\n * Reflect logical activity onto an element without a redundant attribute\n * reaction. Call before appending for the initial value and on ownership\n * changes thereafter; neither path remounts the element.\n */\nexport function setWippyVisibility(element: Element, visible: boolean): void {\n const next = String(visible)\n if (element.getAttribute(WIPPY_VISIBILITY_ATTRIBUTE) !== next)\n element.setAttribute(WIPPY_VISIBILITY_ATTRIBUTE, next)\n}\n","/**\n * Cross-boundary contracts shared between the gen-2-chat host and the\n * `@wippy-fe/*` consumer packages.\n *\n * **Pure contracts and dependency-free DOM helpers.** No framework runtime or\n * `@wippy-fe/proxy` dependency. Importing from this module never loads\n * the proxy globals checker — it is safe to import from anywhere in the\n * host source tree without tripping the `host-import-guard` Vite plugin\n * (this package is on the allowlist).\n *\n * **Status: Draft 1 (preview).** Field names and shapes may change\n * between minor releases until a production consumer drives the corner\n * cases out.\n */\n\n// ─── Layout bus envelopes ────────────────────────────────────────────\n\n/**\n * In-tab broadcast envelope. Same shape on both sides of the bus —\n * iframe panels, host-mounted web components, and headless services\n * all see this object.\n */\nexport interface BroadcastEnvelope {\n channel: string\n payload: unknown\n sourcePanelId: string | null\n targetPanelId: string | null\n}\n\n/**\n * Panel-bound layout bus exposed on `el.__wippyHostBus` for web\n * components mounted as `kind: 'component'` panels. Source / target\n * attribution is set by the host — publishers cannot spoof it.\n */\nexport interface LayoutBusBound {\n /** 1:N broadcast — every other panel receives, sender excluded. */\n broadcast: (channel: string, payload: unknown) => void\n /** 1:1 send — only `target` receives. Unknown targets are no-ops. */\n send: (target: string, channel: string, payload: unknown) => void\n /** Subscribe; returns an `off()` for cleanup. */\n subscribe: (handler: (env: BroadcastEnvelope) => void) => () => void\n}\n\n// ─── Layout mutation targets ─────────────────────────────────────────\n\n/**\n * Drop target positions (relative to a slot rect). Reserved for the\n * future panelDrag capability; declared in v1 for forward compat.\n */\nexport type DropPosition = 'left' | 'right' | 'top' | 'bottom' | 'center' | 'none'\n\n/**\n * Slot size grammar (mirrors `@wippy-fe/layout` `SizeValue`). Tight\n * whitelist — `clamp()`, `minmax()`, `min-content`, `calc()`, em/rem/\n * vh/vw are rejected at validateLayout time. See `@wippy-fe/layout`\n * `helpers.ts` \"Size grammar\" for rationale.\n */\nexport type SizeValue\n = | 'auto'\n | `${number}fr`\n | `${number}px`\n | `${number}%`\n\n/** Pixel-only size, used for min/max and drawer dimensions. */\nexport type PixelSize = `${number}px`\n\n/**\n * Target for addPanel / movePanel. `relativeTo` names a panel already in\n * the tree; `position` picks the drop relative to that panel's rect.\n * Omit `relativeTo` to attach to the root tree (position becomes\n * direction-aware).\n */\nexport interface PanelTarget {\n relativeTo?: string\n position: DropPosition\n size?: SizeValue\n}\n\n// ─── Global-name constants ───────────────────────────────────────────\n//\n// These literal strings name the four `window.*` globals that the host\n// writes and consumer code reads. Both sides MUST agree on the spelling\n// — moving a typo here would silently break communication across the\n// host/proxy boundary. No `Window` augmentation is declared in this\n// package because the augmentation references host-side types\n// (`AppConfig`, `ProxyApiInstance`); host-side `src/shared/constants/global.ts`\n// keeps the `declare global` block.\n\nexport const GLOBAL_CONFIG_VAR = '__WIPPY_APP_CONFIG__' as const\nexport const GLOBAL_PROXY_CONFIG_VAR = '__WIPPY_PROXY_CONFIG__' as const\nexport const GLOBAL_API_PROVIDER = '__WIPPY_APP_API__' as const\nexport const GLOBAL_WEB_COMPONENT_CACHE = '__WIPPY_WEB_COMPONENT_CACHE__' as const\n\n// ─── HTML data-role markers ─────────────────────────────────────────\n//\n// `data-role` attributes that the host and dev-proxy use to find anchor\n// elements in the consuming app's `app.html`. The host removes / inserts\n// scripts at the `@wippy/scripts` element; dev-proxy reads the consuming\n// app's package.json from the `@wippy/package` JSON script (injected at\n// build time by `@wippy-fe/vite-plugin`'s `wippyPackagePlugin()`).\n\nexport const WIPPY_SCRIPTS_DATA_ROLE = '@wippy/scripts' as const\nexport const WIPPY_PACKAGE_DATA_ROLE = '@wippy/package' as const\n\n// ─── Attention Context API ──────────────────────────────────────────\n\nexport type { AttentionInspectionCall, AttentionInspectionNode, AttentionInspectionOperation, AttentionInspectionOutcome, AttentionInspectionReason, AttentionInspectionResult, AttentionInspectionRevisions, AttentionInspectionScope, AttentionLocalTree, AttentionNodeGeometry, AttentionNodeRef, AttentionSearch } from './attention-inspection.ts'\nexport { isAttentionNodeRef } from './attention-inspection.ts'\n\n// ─── Surface portability contract (EE2-2339) ───────────────────────\n\nexport type { AttentionInspectionCancel, AttentionInspectionMessage, AttentionInspectionMount, AttentionInspectionRequest, AttentionInspectionResponse } from './attention-inspection.ts'\n\nexport { isAttentionInspectionMessage, isAttentionInspectionNode, isAttentionInspectionObservation } from './attention-inspection.ts'\n\n// ─── Retained web-component visibility ─────────────────────────────\n\nexport type { AttentionInspectionObservation } from './attention-inspection.ts'\n\nexport type { AttentionLayoutHandle, AttentionLayoutPanel, AttentionLayoutRegistration } from './attention-inspection.ts'\nexport type { AttentionChangeKind, AttentionNotification, AttentionSubscription, AttentionSubscriptionOptions } from './attention-inspection.ts'\nexport { isAttentionChangeKind, isAttentionNotification } from './attention-inspection.ts'\nexport { ATTENTION_RELAY_SCHEMA_V2, attentionRelayByteLength, decodeAttentionRelayMessage, encodeAttentionRelayMessage } from './attention-relay-codec.ts'\nexport type {\n AttentionCandidate,\n AttentionCandidateV2,\n AttentionCapabilityFeature,\n AttentionCapabilityGrantV1,\n AttentionCapabilityRequestV1,\n AttentionCapabilityV1,\n AttentionConfig,\n AttentionContextAttachment,\n AttentionCoordinateSpace,\n AttentionFocus,\n AttentionFocusState,\n AttentionGridEncodingV2,\n AttentionLiveGeometry,\n AttentionLiveQueryCandidateV1,\n AttentionMountCoordinateSpace,\n AttentionMountRef,\n AttentionObservationBatchV1,\n AttentionObservedEvent,\n AttentionOmission,\n AttentionOmissionReason,\n AttentionPathAttributesV3,\n AttentionPathSegment,\n AttentionPathSegmentV3,\n AttentionPoint,\n AttentionPointQueryErrorV1,\n AttentionPointQueryResultV1,\n AttentionPointQueryV1,\n AttentionPointResult,\n AttentionPointResultStatus,\n AttentionPreparedFileV1,\n AttentionQueryBudget,\n AttentionQueryErrorCode,\n AttentionQueryPoint,\n AttentionRealmKind,\n AttentionRect,\n AttentionRectTupleV3,\n AttentionRelayMessageV1,\n AttentionRuntimeBootstrapV1,\n AttentionSampleRefV2,\n AttentionSelection,\n AttentionSelectionClearedState,\n AttentionSelectionQueryErrorV1,\n AttentionSelectionQueryRequestV1,\n AttentionSelectionQueryResultV1,\n AttentionSelectionRange,\n AttentionSelectionSelectedState,\n AttentionSelectionState,\n AttentionSelectionUnknownReason,\n AttentionSelectionUnknownState,\n AttentionSelectionUpdateV1,\n AttentionSnapshotV1,\n AttentionSnapshotV2,\n AttentionSnapshotV3,\n AttentionSnapshotV4,\n AttentionTargetRefV1,\n AttentionTargetSummary,\n AttentionTransform,\n AttentionUiActionCaptureV1,\n AttentionUiActionMessageV1,\n AttentionUiActionMode,\n AttentionUiActionRequestV1,\n AttentionUiActionResultV1,\n AttentionUiActionStatus,\n AttentionVisualCaptureOmissionCode,\n AttentionVisualCaptureScope,\n AttentionVisualContextV1,\n ContextAttachmentEnvelope,\n ContextAttachmentInput,\n ContextAttachmentValidationError,\n ContextAttachmentValidationOptions,\n ContextAttachmentValidationResult,\n JsonPrimitive,\n JsonValue,\n} from './attention.ts'\nexport {\n ATTENTION_CAPABILITY_SCHEMA,\n ATTENTION_CONTEXT_KIND,\n ATTENTION_CONTEXT_SCHEMA,\n ATTENTION_CONTEXT_V2_SCHEMA,\n ATTENTION_CONTEXT_V2_VERSION,\n ATTENTION_CONTEXT_V3_SCHEMA,\n ATTENTION_CONTEXT_V3_VERSION,\n ATTENTION_CONTEXT_V4_SCHEMA,\n ATTENTION_CONTEXT_V4_VERSION,\n ATTENTION_CONTEXT_VERSION,\n ATTENTION_MAX_CANDIDATES,\n ATTENTION_MAX_EXPANDED_BYTES,\n ATTENTION_MAX_PATH_SEGMENTS,\n ATTENTION_MAX_POINT_OMISSIONS,\n ATTENTION_MAX_QUERY_POINTS,\n ATTENTION_MAX_SNAPSHOT_OMISSIONS,\n ATTENTION_RELAY_SCHEMA,\n ATTENTION_RUNTIME_SCHEMA,\n ATTENTION_UI_ACTION_MAX_TTL_MS,\n ATTENTION_UI_ACTION_SCHEMA,\n ATTENTION_V2_MAX_DICTIONARY,\n ATTENTION_V2_MAX_ENVELOPE_BYTES,\n ATTENTION_V2_MAX_MEMBERSHIPS,\n ATTENTION_V2_PREFERRED_CONTENT_BYTES,\n ATTENTION_V3_MAX_ENVELOPE_BYTES,\n ATTENTION_VISUAL_CONTEXT_KIND,\n ATTENTION_VISUAL_CONTEXT_SCHEMA,\n ATTENTION_VISUAL_CONTEXT_VERSION,\n ATTENTION_VISUAL_MAX_BYTES,\n ATTENTION_VISUAL_MAX_HEIGHT_PX,\n ATTENTION_VISUAL_MAX_PIXELS,\n ATTENTION_VISUAL_MAX_TIME_MS,\n ATTENTION_VISUAL_MAX_WIDTH_PX,\n attentionSelectionSemanticKey,\n attentionSemanticPath,\n canonicalJson,\n containsForbiddenContextKey,\n CONTEXT_ATTACHMENT_MAX_BYTES,\n CONTEXT_ATTACHMENTS_MAX_BYTES,\n CONTEXT_ATTACHMENTS_MAX_COUNT,\n createAttentionSampleGrid,\n createContextAttachment,\n decodeAttentionSnapshotV2,\n decodeAttentionSnapshotV2Integrity,\n decodeAttentionSnapshotV3,\n decodeAttentionSnapshotV3Integrity,\n decodeAttentionSnapshotV4,\n decodeAttentionSnapshotV4Integrity,\n encodeAttentionSnapshotV2,\n encodeAttentionSnapshotV3,\n encodeAttentionSnapshotV4,\n isAttentionCapabilityV1,\n isAttentionRelayMessageV1,\n isAttentionRuntimeBootstrapV1,\n isAttentionSelectionState,\n isAttentionSnapshotV1,\n isAttentionSnapshotV2,\n isAttentionSnapshotV3,\n isAttentionSnapshotV4,\n isAttentionStructuralPath,\n isAttentionUiActionMessageV1,\n isAttentionVisualContextV1,\n matchesAttentionQueryResult,\n matchesAttentionSelectionQueryResult,\n parseContextAttachmentContent,\n sha256AttentionPath,\n sha256Json,\n sha256Text,\n utf8ByteLength,\n validateContextAttachments,\n validateContextAttachmentsIntegrity,\n} from './attention.ts'\nexport type {\n PageSizing,\n SurfaceApi,\n SurfaceCapability,\n SurfaceEngine,\n SurfaceManifest,\n SurfaceSnapshot,\n} from './surface.ts'\nexport {\n sizingSupports,\n SURFACE_CAPABILITIES,\n SURFACE_CONTAINER_NAME,\n SURFACE_CONTRACT_VERSION,\n SURFACE_HEIGHT_PROPERTY,\n SURFACE_HEIGHT_UNIT_PROPERTY,\n SURFACE_SUPPORTED_CAPABILITIES,\n SURFACE_WIDTH_PROPERTY,\n SURFACE_WIDTH_UNIT_PROPERTY,\n} from './surface.ts'\nexport { readWippyVisibility, setWippyVisibility, WIPPY_VISIBILITY_ATTRIBUTE } from './visibility.ts'\n","/**\n * `wippyPagePlugin` — for `view.page` packages (those with `wippy.type === 'page'`).\n *\n * Two outputs from a single resolved JSON source:\n * 1. An inline `<script type=\"application/json\" data-role=\"@wippy/package\">`\n * injected at the top of `app.html`'s `<head>`. Read at boot by\n * host-less dev-proxy.\n * 2. `wippy-meta.json` emitted into Vite's `outDir`. Read at request time\n * by `wippy/views` in wippy-hosted mode.\n *\n * Both contain the consumer's `package.json` with every `file://<relative>`\n * reference replaced by its file contents. Single source = identical render\n * in either mode.\n */\n\nimport type { Plugin, ResolvedConfig } from 'vite'\nimport type { LoadedPackage } from './shared'\nimport { resolve } from 'node:path'\nimport { WIPPY_PACKAGE_DATA_ROLE } from '@wippy-fe/shared'\nimport { defangScriptClose, loadPackage, resolveFileRefs, validatePagePackage } from './shared'\n\nexport interface WippyPagePluginOptions {\n /** Override the `package.json` path. Defaults to `<viteRoot>/package.json`. */\n packageJsonPath?: string\n /**\n * If `true`, fail the build when `package.json` is missing. If `false`\n * (default), the plugin silently no-ops — useful for shared configs\n * where some target apps don't need the injection.\n */\n required?: boolean\n}\n\nexport function wippyPagePlugin(options: WippyPagePluginOptions = {}): Plugin {\n let root: string\n // Cache so transformIndexHtml + generateBundle don't re-read package.json\n // and don't double-resolve file:// refs within the same build.\n let resolvedSerialized: string | null = null\n\n function compute(): string | null {\n if (resolvedSerialized !== null)\n return resolvedSerialized\n const pkgPath = options.packageJsonPath ?? resolve(root, 'package.json')\n let loaded: LoadedPackage\n try {\n loaded = loadPackage(pkgPath)\n }\n catch (err) {\n if (options.required)\n throw new Error(`[@wippy-fe/vite-plugin] failed to read ${pkgPath}: ${(err as Error).message}`)\n return null\n }\n // Strict spec validation — throws on any wippy-block shape violation\n // (wrong `wippy.type`, missing `wippy.path`, forbidden `wippy.tagName`,\n // etc.). We throw rather than warn so misconfigurations fail the build\n // instead of silently shipping broken metadata. See shared.ts for the\n // full rule set.\n validatePagePackage(loaded.pkg, pkgPath)\n\n // `file://` basename convention violations also throw: the\n // `.do-not-link.` infix is the AI-readable directive that prevents\n // agents from `<link>`-ing the file directly. A consumer using\n // `file://theme.css` defeats that signal even though the resolved\n // content is correct, so we fail the build to force the rename.\n const resolvedPkg = resolveFileRefs(loaded.pkg, loaded.baseDir, (msg) => {\n throw new Error(msg)\n })\n resolvedSerialized = JSON.stringify(resolvedPkg)\n return resolvedSerialized\n }\n\n // Build-time reframed-incompatibility scan (EE2-2313). Warn once per hostile\n // API so a fragment-incompatible page is caught at BUILD, not in prod. Skipped\n // when the page already opts out (wippy.renderEngine: 'iframe').\n const warnedApis = new Set<string>()\n let optedOut: boolean | null = null\n const isOptedOut = (): boolean => {\n if (optedOut === null) {\n try {\n optedOut = (JSON.parse(compute() ?? '{}')?.wippy?.renderEngine) === 'iframe'\n }\n catch {\n optedOut = false\n }\n }\n return optedOut\n }\n\n return {\n name: '@wippy-fe/vite-plugin:wippy-page',\n enforce: 'pre',\n configResolved(config: ResolvedConfig) {\n root = config.root\n resolvedSerialized = null\n },\n transform(code: string, id: string) {\n if (/node_modules/.test(id) || !/\\.(?:vue|m?[jt]sx?)$/.test(id) || isOptedOut())\n return null\n const hostile: Array<[RegExp, string]> = [\n [/\\belementFromPoint\\b/, 'elementFromPoint (hit-testing returns null in a reframed realm)'],\n [/\\bwindow\\.scroll(?:X|Y|To|By)?\\b/, 'window scroll geometry (always 0 in a reframed realm)'],\n [/\\bmatchMedia\\s*\\(/, 'matchMedia (resolves against the HOST viewport, not the fragment panel)'],\n [/\\b\\d+(?:\\.\\d+)?v[hw]\\b/, 'vh/vw units (resolve against the HOST viewport)'],\n [/position\\s*:\\s*fixed/, 'position:fixed (pins to the HOST viewport, not the fragment panel)'],\n ]\n for (const [re, msg] of hostile) {\n if (re.test(code) && !warnedApis.has(msg)) {\n warnedApis.add(msg)\n this.warn(`[@wippy-fe/vite-plugin] this view.page uses ${msg}. Such APIs misbehave inside a Web Fragment. If it will run under the fragment engine, set \\`wippy.renderEngine: \"iframe\"\\` in package.json to render it as a legacy iframe. [EE2-2313]`)\n }\n }\n return null\n },\n transformIndexHtml: {\n order: 'pre',\n handler(html) {\n const json = compute()\n if (json === null)\n return html\n const safeJson = defangScriptClose(json)\n const tag = `<script type=\"application/json\" data-role=\"${WIPPY_PACKAGE_DATA_ROLE}\">${safeJson}</script>`\n if (html.includes('<head>'))\n return html.replace('<head>', `<head>\\n ${tag}`)\n return tag + html\n },\n },\n generateBundle() {\n const json = compute()\n if (json === null)\n return\n this.emitFile({\n type: 'asset',\n fileName: 'wippy-meta.json',\n source: json,\n })\n },\n }\n}\n","/**\n * Shared helpers for `wippyPagePlugin` and `wippyComponentPlugin`.\n *\n * Both plugins produce `dist/wippy-meta.json` — a resolved copy of the\n * consumer's `package.json` with every `file://<relative-path>` reference\n * replaced by the referenced file's UTF-8 contents. The page plugin\n * additionally injects the same resolved JSON into `app.html` as an\n * `<script type=\"application/json\" data-role=\"@wippy/package\">` so host-less\n * mode (gen-2-chat dev-proxy) can read it synchronously at boot.\n *\n * `wippy-meta.json` is the MANDATORY contract between consumers and\n * `wippy/views`. See `web_components.spec.md` § \"Package metadata: source of\n * truth\" for the full spec.\n */\n\nimport { readFileSync } from 'node:fs'\nimport { basename, dirname, resolve } from 'node:path'\n\nexport interface LoadedPackage {\n /** The parsed `package.json` as a JS object. */\n pkg: Record<string, unknown>\n /**\n * Absolute path to the directory containing `package.json` — used as the\n * base for `file://` reference resolution.\n */\n baseDir: string\n}\n\n/**\n * Read + parse `package.json` from disk. Throws if the file is missing or\n * not valid JSON — the build is expected to fail loud rather than silently\n * skip the package.\n */\nexport function loadPackage(packageJsonPath: string): LoadedPackage {\n const raw = readFileSync(packageJsonPath, 'utf-8')\n const pkg = JSON.parse(raw) as Record<string, unknown>\n return { pkg, baseDir: dirname(packageJsonPath) }\n}\n\n/**\n * camelCase → kebab-case conversion used by the `*.do-not-link.<ext>`\n * naming convention. Treats consecutive capitals as a single word so\n * `customCSS` → `custom-css` (not `custom-c-s-s`). Matches lodash's\n * kebabCase semantics for these specific input shapes.\n *\n * Examples codified in tests:\n * - `customCSS` → `custom-css`\n * - `customCss` → `custom-css`\n * - `cssVariables` → `css-variables`\n * - `tailwindConfig` → `tailwind-config`\n * - `description` → `description`\n * - `URLPath` → `url-path`\n */\nexport function kebabize(camel: string): string {\n return camel\n // Insert a hyphen between a run of capitals and a following Capital+lowercase:\n // \"URLPath\" → \"URL-Path\"\n // \"customCSS\" stays \"customCSS\" here (no following lowercase)\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')\n // Insert a hyphen between a lowercase/digit and a following capital:\n // \"customCSS\" → \"custom-CSS\"\n // \"URL-Path\" → \"URL-Path\" (unchanged — already split)\n .replace(/([a-z\\d])([A-Z])/g, '$1-$2')\n .toLowerCase()\n}\n\n/**\n * Validator used by `resolveFileRefs` to surface mismatches between a\n * `file://` reference's basename and the canonical `<field-kebab>.do-not-link.<ext>`\n * convention. Returns the expected basename (for use in the warning\n * message) or `null` if the actual basename already conforms.\n *\n * Convention: basename without extension MUST end with `.do-not-link` and\n * its prefix MUST equal `kebabize(fieldName)`. Examples:\n * - field `customCSS`, file `custom-css.do-not-link.css` → null (ok)\n * - field `customCSS`, file `my-theme.css` → \"custom-css.do-not-link.css\"\n * - field `customCSS`, file `custom-css.css` → \"custom-css.do-not-link.css\"\n *\n * `null` field name (top-level / array index) skips the check.\n */\nexport function expectedDoNotLinkBasename(fieldName: string | null, actualBasename: string): string | null {\n if (!fieldName)\n return null\n const dot = actualBasename.lastIndexOf('.')\n if (dot <= 0)\n return `${kebabize(fieldName)}.do-not-link.<ext>`\n const ext = actualBasename.slice(dot + 1)\n const expected = `${kebabize(fieldName)}.do-not-link.${ext}`\n if (actualBasename === expected)\n return null\n return expected\n}\n\n/**\n * Recursively replace every string value of shape `file://<relative-path>`\n * with the referenced file's UTF-8 contents. Paths are resolved against\n * `baseDir` (typically the directory containing `package.json`).\n *\n * Non-string, non-array, non-object values pass through unchanged. Missing\n * files throw — the convention is that `file://` refs are load-bearing.\n *\n * `onWarn` (optional) is called when a `file://` basename doesn't match the\n * canonical `<field-kebab>.do-not-link.<ext>` convention. The warning is\n * advisory — resolution still succeeds — so consumers can opt out by not\n * passing the callback.\n */\nexport function resolveFileRefs<T>(\n node: T,\n baseDir: string,\n onWarn?: (msg: string) => void,\n fieldName: string | null = null,\n): T {\n if (typeof node === 'string' && node.startsWith('file://')) {\n const rel = node.slice('file://'.length)\n const abs = resolve(baseDir, rel)\n if (onWarn) {\n const expected = expectedDoNotLinkBasename(fieldName, basename(rel))\n if (expected) {\n onWarn(`[@wippy-fe/vite-plugin] file:// basename \"${basename(rel)}\" for field \"${fieldName}\" does not match the *.do-not-link.<ext> convention. Expected: \"${expected}\". See web_components.spec.md § \"Package metadata: source of truth\".`)\n }\n }\n return readFileSync(abs, 'utf-8') as unknown as T\n }\n if (Array.isArray(node))\n return node.map(v => resolveFileRefs(v, baseDir, onWarn, fieldName)) as unknown as T\n if (node && typeof node === 'object') {\n return Object.fromEntries(\n Object.entries(node as Record<string, unknown>)\n .map(([k, v]) => [k, resolveFileRefs(v, baseDir, onWarn, k)]),\n ) as unknown as T\n }\n return node\n}\n\n/**\n * Defang `</script>` sequences inside a JSON string so it's safe to embed\n * verbatim inside an inline `<script type=\"application/json\">` tag. Case\n * insensitive (`</SCRIPT`, `</Script`, etc. all need escaping).\n */\nexport function defangScriptClose(json: string): string {\n return json.replace(/<\\/script/gi, '<\\\\/script')\n}\n\n/**\n * Validate a consumer's `package.json` against the `wippy-component-1.0`\n * spec FOR A PAGE PACKAGE. Throws with a clear, actionable message on any\n * violation. Called by `wippyPagePlugin` at build time so misconfigurations\n * fail the build instead of silently shipping broken metadata.\n *\n * We throw rather than warn because nobody reads build warnings — they\n * scroll past in CI output and get filtered out of IDE diagnostics. A\n * field-shape mistake that warns today is a real bug tomorrow.\n *\n * Rules enforced (must all hold for a page):\n * - Top-level `name` and `version` must be strings.\n * - `wippy` must be a plain object.\n * - `wippy.type`, when set, MUST equal `'page'` (not `'component'`,\n * `'widget'`, or anything else).\n * - `wippy.path` is REQUIRED for pages (HTML entry path).\n * - `wippy.tagName` is FORBIDDEN for pages (custom-element-tag is a\n * component-only concept).\n * - `specification`, when set, MUST equal `'wippy-component-1.0'`.\n */\nexport function validatePagePackage(pkg: Record<string, unknown>, packageJsonPath: string): void {\n const prefix = `[@wippy-fe/vite-plugin] ${packageJsonPath}:`\n\n if (typeof pkg.name !== 'string' || pkg.name.length === 0)\n throw new Error(`${prefix} \\`name\\` is required and must be a non-empty string`)\n if (typeof pkg.version !== 'string' || pkg.version.length === 0)\n throw new Error(`${prefix} \\`version\\` is required and must be a non-empty string`)\n if (pkg.specification !== undefined && pkg.specification !== 'wippy-component-1.0')\n throw new Error(`${prefix} \\`specification\\` must be \"wippy-component-1.0\" (got ${JSON.stringify(pkg.specification)})`)\n\n const wippy = pkg.wippy\n if (wippy === undefined || wippy === null)\n throw new Error(`${prefix} top-level \\`wippy\\` block is required for a page package using wippyPagePlugin()`)\n if (typeof wippy !== 'object' || Array.isArray(wippy))\n throw new Error(`${prefix} \\`wippy\\` must be a JSON object (got ${Array.isArray(wippy) ? 'array' : typeof wippy})`)\n\n const w = wippy as Record<string, unknown>\n\n if (w.type !== undefined && w.type !== 'page')\n throw new Error(`${prefix} \\`wippy.type\\` is \"${String(w.type)}\" but wippyPagePlugin() was used. Either set \\`wippy.type: \"page\"\\` or switch to wippyComponentPlugin() for components/widgets.`)\n\n if (w.path === undefined)\n throw new Error(`${prefix} \\`wippy.path\\` is required for a page package (the relative path to the HTML entry, e.g. \"dist/app.html\")`)\n if (typeof w.path !== 'string' || w.path.length === 0)\n throw new Error(`${prefix} \\`wippy.path\\` must be a non-empty string (got ${JSON.stringify(w.path)})`)\n\n if (w.tagName !== undefined)\n throw new Error(`${prefix} \\`wippy.tagName\\` is set on a page package but the field is component-only. Either remove it or switch to wippyComponentPlugin().`)\n\n // EE2-2313: per-page render engine. The string form must be a known value; the\n // reserved object form ({ route?, artifact? }) is permitted but not read yet.\n if (w.renderEngine !== undefined && typeof w.renderEngine === 'string'\n && w.renderEngine !== 'auto' && w.renderEngine !== 'iframe' && w.renderEngine !== 'fragment') {\n throw new Error(`${prefix} \\`wippy.renderEngine\\` must be \"auto\", \"iframe\", or \"fragment\" (got ${JSON.stringify(w.renderEngine)}). Absent = \"auto\" (follow the deployment's global engine); use \"iframe\" to opt a fragment-incompatible page out.`)\n }\n}\n\n/**\n * Validate a consumer's `package.json` for a component/widget package using\n * `wippyComponentPlugin`. Throws on any violation. Same rationale as\n * `validatePagePackage` — fail loud at build time.\n *\n * Rules enforced (must all hold for a component):\n * - Top-level `name` and `version` must be strings.\n * - `wippy` must be a plain object.\n * - `wippy.type`, when set, MUST be one of `'component'` or `'widget'` (the\n * spec uses both interchangeably for ESM-shipped custom elements).\n * - `wippy.tagName` is REQUIRED for components — without it the host has\n * no element name to register against.\n * - `wippy.path` is FORBIDDEN for components (HTML-entry is a page-only\n * concept; components use top-level `browser` for the ESM entry).\n * - `specification`, when set, MUST equal `'wippy-component-1.0'`.\n * - `browser` SHOULD be set (the ESM entry path); only warn-by-throw if\n * absent because the plugin still emits useful meta without it.\n */\nexport function validateComponentPackage(pkg: Record<string, unknown>, packageJsonPath: string): void {\n const prefix = `[@wippy-fe/vite-plugin] ${packageJsonPath}:`\n\n if (typeof pkg.name !== 'string' || pkg.name.length === 0)\n throw new Error(`${prefix} \\`name\\` is required and must be a non-empty string`)\n if (typeof pkg.version !== 'string' || pkg.version.length === 0)\n throw new Error(`${prefix} \\`version\\` is required and must be a non-empty string`)\n if (pkg.specification !== undefined && pkg.specification !== 'wippy-component-1.0')\n throw new Error(`${prefix} \\`specification\\` must be \"wippy-component-1.0\" (got ${JSON.stringify(pkg.specification)})`)\n\n const wippy = pkg.wippy\n if (wippy === undefined || wippy === null)\n throw new Error(`${prefix} top-level \\`wippy\\` block is required for a component package using wippyComponentPlugin()`)\n if (typeof wippy !== 'object' || Array.isArray(wippy))\n throw new Error(`${prefix} \\`wippy\\` must be a JSON object (got ${Array.isArray(wippy) ? 'array' : typeof wippy})`)\n\n const w = wippy as Record<string, unknown>\n\n if (w.type !== undefined && w.type !== 'component' && w.type !== 'widget')\n throw new Error(`${prefix} \\`wippy.type\\` is \"${String(w.type)}\" but wippyComponentPlugin() was used. Either set \\`wippy.type: \"component\"\\` (or \"widget\") or switch to wippyPagePlugin() for pages.`)\n\n if (w.tagName === undefined)\n throw new Error(`${prefix} \\`wippy.tagName\\` is required for a component package (the custom-element tag name, e.g. \"example-mermaid\"). Without it the host cannot register the element.`)\n if (typeof w.tagName !== 'string' || w.tagName.length === 0)\n throw new Error(`${prefix} \\`wippy.tagName\\` must be a non-empty string (got ${JSON.stringify(w.tagName)})`)\n // Custom Elements spec (PotentialCustomElementName):\n // - MUST start with an ASCII lowercase letter [a-z]\n // - MUST contain a hyphen\n // - MAY contain ASCII lowercase letters, digits, or hyphens\n // (The full spec also allows ., _, and some Unicode ranges, but in practice\n // Wippy tag names only ever use [a-z0-9-]. We keep the check tight rather\n // than full-spec because a \"FOO-BAR\" or \"-leading\" passing validation today\n // would silently break tomorrow when `customElements.define()` rejects it.)\n //\n // Mirror twin: `@wippy-fe/proxy`'s `vue-warn-suppressor.ts` uses an\n // equivalent regex (`/^[a-z][a-z0-9]*-[a-z0-9-]*$/`) to decide whether\n // an unresolved Vue tag \"looks like a pending-registration custom\n // element\". The two patterns match exactly the same name shape — keep\n // them in sync. Functionally identical; differ only in character-class\n // notation (`[\\da-z]` vs `[a-z0-9]`).\n if (!/^[a-z][\\da-z]*-[-\\da-z]*$/.test(w.tagName))\n throw new Error(`${prefix} \\`wippy.tagName\\` \"${w.tagName}\" is not a valid Custom Elements name. Must start with an ASCII lowercase letter, contain a hyphen, and use only lowercase letters, digits, and hyphens. Examples: \"example-mermaid\", \"my-widget-v2\".`)\n // Reserved names that browsers refuse to register (HTML5 spec):\n const reserved = new Set([\n 'annotation-xml',\n 'color-profile',\n 'font-face',\n 'font-face-src',\n 'font-face-uri',\n 'font-face-format',\n 'font-face-name',\n 'missing-glyph',\n ])\n if (reserved.has(w.tagName))\n throw new Error(`${prefix} \\`wippy.tagName\\` \"${w.tagName}\" is a reserved HTML name; browsers will refuse to register it.`)\n\n if (w.path !== undefined)\n throw new Error(`${prefix} \\`wippy.path\\` is set on a component package but the field is page-only. Components use top-level \\`browser\\` for the ESM entry path. Either remove \\`wippy.path\\` or switch to wippyPagePlugin().`)\n\n // EE2-2313: render engine applies to view.page only — a view.component's render\n // path is fixed, so the flag is meaningless (and would silently do nothing) here.\n if (w.renderEngine !== undefined)\n throw new Error(`${prefix} \\`wippy.renderEngine\\` is set on a component package but the field is page-only (a view.component's render path is fixed). Remove it.`)\n}\n","/**\n * `wippyComponentPlugin` — for `view.component` packages (those with\n * `wippy.type === 'component'`).\n *\n * Components ship a JS module, not an HTML entry — there's nothing to inject\n * an inline `<script>` into. The plugin's only job is to emit `wippy-meta.json`\n * into Vite's `outDir` for `wippy/views` to read at request time.\n *\n * The `wippy-meta.json` file is the MANDATORY contract between components and\n * `wippy/views`. See `web_components.spec.md` § \"Package metadata: source of\n * truth\". Without it, `views` falls back to its legacy YAML-synthesis path,\n * which is deprecated and exists only for migration.\n */\n\nimport type { Plugin, ResolvedConfig } from 'vite'\nimport type { LoadedPackage } from './shared'\nimport { resolve } from 'node:path'\nimport { loadPackage, resolveFileRefs, validateComponentPackage } from './shared'\n\nexport interface WippyComponentPluginOptions {\n /** Override the `package.json` path. Defaults to `<viteRoot>/package.json`. */\n packageJsonPath?: string\n /**\n * If `true`, fail the build when `package.json` is missing. If `false`\n * (default), the plugin silently no-ops.\n */\n required?: boolean\n}\n\nexport function wippyComponentPlugin(options: WippyComponentPluginOptions = {}): Plugin {\n let root: string\n\n return {\n name: '@wippy-fe/vite-plugin:wippy-component',\n enforce: 'pre',\n configResolved(config: ResolvedConfig) {\n root = config.root\n },\n generateBundle() {\n const pkgPath = options.packageJsonPath ?? resolve(root, 'package.json')\n let loaded: LoadedPackage\n try {\n loaded = loadPackage(pkgPath)\n }\n catch (err) {\n if (options.required)\n throw new Error(`[@wippy-fe/vite-plugin] failed to read ${pkgPath}: ${(err as Error).message}`)\n return\n }\n // Strict spec validation — throws on any wippy-block shape violation\n // (wrong `wippy.type`, missing `wippy.tagName`, forbidden `wippy.path`,\n // tagName without hyphen, etc.). We throw rather than warn so\n // misconfigurations fail the build instead of silently shipping broken\n // metadata. See shared.ts for the full rule set.\n validateComponentPackage(loaded.pkg, pkgPath)\n\n // `file://` basename convention violations throw — see page-plugin's\n // identical comment for the rationale.\n const resolvedPkg = resolveFileRefs(loaded.pkg, loaded.baseDir, (msg) => {\n throw new Error(msg)\n })\n this.emitFile({\n type: 'asset',\n fileName: 'wippy-meta.json',\n source: JSON.stringify(resolvedPkg),\n })\n },\n }\n}\n","/**\n * Vite plugins for Wippy view.page and view.component packages.\n *\n * Both plugins emit `dist/wippy-meta.json` — the consumer's `package.json`\n * with every `file://<relative-path>` reference replaced by the referenced\n * file's UTF-8 contents. `wippy-meta.json` is the MANDATORY contract between\n * consumers and `wippy/views` ≥ 1.0.31. See `web_components.spec.md` §\n * \"Package metadata: source of truth\" for the full spec.\n *\n * - `wippyPagePlugin()` — for `view.page` packages. Emits `wippy-meta.json`\n * AND injects an inline `<script data-role=\"@wippy/package\">` into the\n * built `app.html` (for host-less mode).\n * - `wippyComponentPlugin()` — for `view.component` packages. Emits\n * `wippy-meta.json` only; components have no HTML entry.\n *\n * Convention: bulky string fields (`customCSS`, `description`, etc.) should\n * be moved into separate files referenced via `file://` and named\n * `<field-name-kebab>.do-not-link.<ext>` — basename matches the kebab-case\n * wippy block field name (e.g. `customCSS` → `custom-css.do-not-link.css`,\n * `description` → `description.do-not-link.md`). The `.do-not-link.` infix\n * is what stops AI agents from `<link>`-ing the file directly into app.html.\n * The proxy injects the content at runtime from the resolved package metadata.\n *\n * **Status: Draft 1 (preview).** API may change between minor releases.\n */\n\nimport type { Plugin } from 'vite'\nimport { WIPPY_PACKAGE_DATA_ROLE } from '@wippy-fe/shared'\nimport { wippyPagePlugin } from './page-plugin'\n\nexport { WIPPY_PACKAGE_DATA_ROLE }\nexport { wippyComponentPlugin } from './component-plugin'\nexport type { WippyComponentPluginOptions } from './component-plugin'\nexport { wippyPagePlugin } from './page-plugin'\nexport type { WippyPagePluginOptions } from './page-plugin'\nexport type { WippyPagePluginOptions as WippyPackagePluginOptions } from './page-plugin'\n\n/**\n * @deprecated Use `wippyPagePlugin()` instead. The new name disambiguates\n * the page-side plugin from `wippyComponentPlugin()` introduced alongside it.\n * This alias preserves backwards compatibility for existing `vite.config.ts`\n * files and will be removed in a future major.\n */\nexport function wippyPackagePlugin(...args: Parameters<typeof wippyPagePlugin>): Plugin {\n return wippyPagePlugin(...args)\n}\n// Re-export the strict validators so CI tooling / external linters can\n// reuse the same rules the plugins enforce at build time.\nexport { validateComponentPackage, validatePagePackage } from './shared'\n"],"mappings":";AACO,IAAM,gCAAgC,KAAK;AAC3C,IAAM,+BAA+B,KAAK;AAU1C,IAAM,uCAAuC,KAAK;AAClD,IAAM,kCAAkC,KAAK;AAC7C,IAAM,kCAAkC,KAAK;AAC7C,IAAM,+BAA+B,MAAM;AAiB3C,IAAM,6BAA6B,OAAO;AE3BjD,IAAM,iBAAiB;AACvB,IAAM,qBAAqB,IAAI;AGgGxB,IAAM,0BAA0B;;;ACrFvC,SAAS,WAAAA,gBAAe;;;ACFxB,SAAS,oBAAoB;AAC7B,SAAS,UAAU,SAAS,eAAe;AAiBpC,SAAS,YAAY,iBAAwC;AAClE,QAAM,MAAM,aAAa,iBAAiB,OAAO;AACjD,QAAM,MAAM,KAAK,MAAM,GAAG;AAC1B,SAAO,EAAE,KAAK,SAAS,QAAQ,eAAe,EAAE;AAClD;AAgBO,SAAS,SAAS,OAAuB;AAC9C,SAAO,MAIJ,QAAQ,yBAAyB,OAAO,EAIxC,QAAQ,qBAAqB,OAAO,EACpC,YAAY;AACjB;AAgBO,SAAS,0BAA0B,WAA0B,gBAAuC;AACzG,MAAI,CAAC;AACH,WAAO;AACT,QAAM,MAAM,eAAe,YAAY,GAAG;AAC1C,MAAI,OAAO;AACT,WAAO,GAAG,SAAS,SAAS,CAAC;AAC/B,QAAM,MAAM,eAAe,MAAM,MAAM,CAAC;AACxC,QAAM,WAAW,GAAG,SAAS,SAAS,CAAC,gBAAgB,GAAG;AAC1D,MAAI,mBAAmB;AACrB,WAAO;AACT,SAAO;AACT;AAeO,SAAS,gBACd,MACA,SACA,QACA,YAA2B,MACxB;AACH,MAAI,OAAO,SAAS,YAAY,KAAK,WAAW,SAAS,GAAG;AAC1D,UAAM,MAAM,KAAK,MAAM,UAAU,MAAM;AACvC,UAAM,MAAM,QAAQ,SAAS,GAAG;AAChC,QAAI,QAAQ;AACV,YAAM,WAAW,0BAA0B,WAAW,SAAS,GAAG,CAAC;AACnE,UAAI,UAAU;AACZ,eAAO,6CAA6C,SAAS,GAAG,CAAC,gBAAgB,SAAS,mEAAmE,QAAQ,yEAAsE;AAAA,MAC7O;AAAA,IACF;AACA,WAAO,aAAa,KAAK,OAAO;AAAA,EAClC;AACA,MAAI,MAAM,QAAQ,IAAI;AACpB,WAAO,KAAK,IAAI,OAAK,gBAAgB,GAAG,SAAS,QAAQ,SAAS,CAAC;AACrE,MAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,WAAO,OAAO;AAAA,MACZ,OAAO,QAAQ,IAA+B,EAC3C,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,gBAAgB,GAAG,SAAS,QAAQ,CAAC,CAAC,CAAC;AAAA,IAChE;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,kBAAkB,MAAsB;AACtD,SAAO,KAAK,QAAQ,eAAe,YAAY;AACjD;AAsBO,SAAS,oBAAoB,KAA8B,iBAA+B;AAC/F,QAAM,SAAS,2BAA2B,eAAe;AAEzD,MAAI,OAAO,IAAI,SAAS,YAAY,IAAI,KAAK,WAAW;AACtD,UAAM,IAAI,MAAM,GAAG,MAAM,sDAAsD;AACjF,MAAI,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,WAAW;AAC5D,UAAM,IAAI,MAAM,GAAG,MAAM,yDAAyD;AACpF,MAAI,IAAI,kBAAkB,UAAa,IAAI,kBAAkB;AAC3D,UAAM,IAAI,MAAM,GAAG,MAAM,yDAAyD,KAAK,UAAU,IAAI,aAAa,CAAC,GAAG;AAExH,QAAM,QAAQ,IAAI;AAClB,MAAI,UAAU,UAAa,UAAU;AACnC,UAAM,IAAI,MAAM,GAAG,MAAM,mFAAmF;AAC9G,MAAI,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK;AAClD,UAAM,IAAI,MAAM,GAAG,MAAM,yCAAyC,MAAM,QAAQ,KAAK,IAAI,UAAU,OAAO,KAAK,GAAG;AAEpH,QAAM,IAAI;AAEV,MAAI,EAAE,SAAS,UAAa,EAAE,SAAS;AACrC,UAAM,IAAI,MAAM,GAAG,MAAM,uBAAuB,OAAO,EAAE,IAAI,CAAC,iIAAiI;AAEjM,MAAI,EAAE,SAAS;AACb,UAAM,IAAI,MAAM,GAAG,MAAM,4GAA4G;AACvI,MAAI,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,WAAW;AAClD,UAAM,IAAI,MAAM,GAAG,MAAM,mDAAmD,KAAK,UAAU,EAAE,IAAI,CAAC,GAAG;AAEvG,MAAI,EAAE,YAAY;AAChB,UAAM,IAAI,MAAM,GAAG,MAAM,oIAAoI;AAI/J,MAAI,EAAE,iBAAiB,UAAa,OAAO,EAAE,iBAAiB,YACzD,EAAE,iBAAiB,UAAU,EAAE,iBAAiB,YAAY,EAAE,iBAAiB,YAAY;AAC9F,UAAM,IAAI,MAAM,GAAG,MAAM,wEAAwE,KAAK,UAAU,EAAE,YAAY,CAAC,mHAAmH;AAAA,EACpP;AACF;AAoBO,SAAS,yBAAyB,KAA8B,iBAA+B;AACpG,QAAM,SAAS,2BAA2B,eAAe;AAEzD,MAAI,OAAO,IAAI,SAAS,YAAY,IAAI,KAAK,WAAW;AACtD,UAAM,IAAI,MAAM,GAAG,MAAM,sDAAsD;AACjF,MAAI,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,WAAW;AAC5D,UAAM,IAAI,MAAM,GAAG,MAAM,yDAAyD;AACpF,MAAI,IAAI,kBAAkB,UAAa,IAAI,kBAAkB;AAC3D,UAAM,IAAI,MAAM,GAAG,MAAM,yDAAyD,KAAK,UAAU,IAAI,aAAa,CAAC,GAAG;AAExH,QAAM,QAAQ,IAAI;AAClB,MAAI,UAAU,UAAa,UAAU;AACnC,UAAM,IAAI,MAAM,GAAG,MAAM,6FAA6F;AACxH,MAAI,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK;AAClD,UAAM,IAAI,MAAM,GAAG,MAAM,yCAAyC,MAAM,QAAQ,KAAK,IAAI,UAAU,OAAO,KAAK,GAAG;AAEpH,QAAM,IAAI;AAEV,MAAI,EAAE,SAAS,UAAa,EAAE,SAAS,eAAe,EAAE,SAAS;AAC/D,UAAM,IAAI,MAAM,GAAG,MAAM,uBAAuB,OAAO,EAAE,IAAI,CAAC,uIAAuI;AAEvM,MAAI,EAAE,YAAY;AAChB,UAAM,IAAI,MAAM,GAAG,MAAM,gKAAgK;AAC3L,MAAI,OAAO,EAAE,YAAY,YAAY,EAAE,QAAQ,WAAW;AACxD,UAAM,IAAI,MAAM,GAAG,MAAM,sDAAsD,KAAK,UAAU,EAAE,OAAO,CAAC,GAAG;AAgB7G,MAAI,CAAC,4BAA4B,KAAK,EAAE,OAAO;AAC7C,UAAM,IAAI,MAAM,GAAG,MAAM,uBAAuB,EAAE,OAAO,uMAAuM;AAElQ,QAAM,WAAW,oBAAI,IAAI;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,MAAI,SAAS,IAAI,EAAE,OAAO;AACxB,UAAM,IAAI,MAAM,GAAG,MAAM,uBAAuB,EAAE,OAAO,iEAAiE;AAE5H,MAAI,EAAE,SAAS;AACb,UAAM,IAAI,MAAM,GAAG,MAAM,qMAAqM;AAIhO,MAAI,EAAE,iBAAiB;AACrB,UAAM,IAAI,MAAM,GAAG,MAAM,wIAAwI;AACrK;;;ADzPO,SAAS,gBAAgB,UAAkC,CAAC,GAAW;AAC5E,MAAI;AAGJ,MAAI,qBAAoC;AAExC,WAAS,UAAyB;AAChC,QAAI,uBAAuB;AACzB,aAAO;AACT,UAAM,UAAU,QAAQ,mBAAmBC,SAAQ,MAAM,cAAc;AACvE,QAAI;AACJ,QAAI;AACF,eAAS,YAAY,OAAO;AAAA,IAC9B,SACO,KAAK;AACV,UAAI,QAAQ;AACV,cAAM,IAAI,MAAM,0CAA0C,OAAO,KAAM,IAAc,OAAO,EAAE;AAChG,aAAO;AAAA,IACT;AAMA,wBAAoB,OAAO,KAAK,OAAO;AAOvC,UAAM,cAAc,gBAAgB,OAAO,KAAK,OAAO,SAAS,CAAC,QAAQ;AACvE,YAAM,IAAI,MAAM,GAAG;AAAA,IACrB,CAAC;AACD,yBAAqB,KAAK,UAAU,WAAW;AAC/C,WAAO;AAAA,EACT;AAKA,QAAM,aAAa,oBAAI,IAAY;AACnC,MAAI,WAA2B;AAC/B,QAAM,aAAa,MAAe;AAChC,QAAI,aAAa,MAAM;AACrB,UAAI;AACF,mBAAY,KAAK,MAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,iBAAkB;AAAA,MACtE,QACM;AACJ,mBAAW;AAAA,MACb;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,eAAe,QAAwB;AACrC,aAAO,OAAO;AACd,2BAAqB;AAAA,IACvB;AAAA,IACA,UAAU,MAAc,IAAY;AAClC,UAAI,eAAe,KAAK,EAAE,KAAK,CAAC,uBAAuB,KAAK,EAAE,KAAK,WAAW;AAC5E,eAAO;AACT,YAAM,UAAmC;AAAA,QACvC,CAAC,wBAAwB,iEAAiE;AAAA,QAC1F,CAAC,oCAAoC,uDAAuD;AAAA,QAC5F,CAAC,qBAAqB,yEAAyE;AAAA,QAC/F,CAAC,0BAA0B,iDAAiD;AAAA,QAC5E,CAAC,wBAAwB,oEAAoE;AAAA,MAC/F;AACA,iBAAW,CAAC,IAAI,GAAG,KAAK,SAAS;AAC/B,YAAI,GAAG,KAAK,IAAI,KAAK,CAAC,WAAW,IAAI,GAAG,GAAG;AACzC,qBAAW,IAAI,GAAG;AAClB,eAAK,KAAK,+CAA+C,GAAG,yLAAyL;AAAA,QACvP;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IACA,oBAAoB;AAAA,MAClB,OAAO;AAAA,MACP,QAAQ,MAAM;AACZ,cAAM,OAAO,QAAQ;AACrB,YAAI,SAAS;AACX,iBAAO;AACT,cAAM,WAAW,kBAAkB,IAAI;AACvC,cAAM,MAAM,8CAA8C,uBAAuB,KAAK,QAAQ;AAC9F,YAAI,KAAK,SAAS,QAAQ;AACxB,iBAAO,KAAK,QAAQ,UAAU;AAAA,MAAe,GAAG,EAAE;AACpD,eAAO,MAAM;AAAA,MACf;AAAA,IACF;AAAA,IACA,iBAAiB;AACf,YAAM,OAAO,QAAQ;AACrB,UAAI,SAAS;AACX;AACF,WAAK,SAAS;AAAA,QACZ,MAAM;AAAA,QACN,UAAU;AAAA,QACV,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;AExHA,SAAS,WAAAC,gBAAe;AAajB,SAAS,qBAAqB,UAAuC,CAAC,GAAW;AACtF,MAAI;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,eAAe,QAAwB;AACrC,aAAO,OAAO;AAAA,IAChB;AAAA,IACA,iBAAiB;AACf,YAAM,UAAU,QAAQ,mBAAmBC,SAAQ,MAAM,cAAc;AACvE,UAAI;AACJ,UAAI;AACF,iBAAS,YAAY,OAAO;AAAA,MAC9B,SACO,KAAK;AACV,YAAI,QAAQ;AACV,gBAAM,IAAI,MAAM,0CAA0C,OAAO,KAAM,IAAc,OAAO,EAAE;AAChG;AAAA,MACF;AAMA,+BAAyB,OAAO,KAAK,OAAO;AAI5C,YAAM,cAAc,gBAAgB,OAAO,KAAK,OAAO,SAAS,CAAC,QAAQ;AACvE,cAAM,IAAI,MAAM,GAAG;AAAA,MACrB,CAAC;AACD,WAAK,SAAS;AAAA,QACZ,MAAM;AAAA,QACN,UAAU;AAAA,QACV,QAAQ,KAAK,UAAU,WAAW;AAAA,MACpC,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;ACzBO,SAAS,sBAAsB,MAAkD;AACtF,SAAO,gBAAgB,GAAG,IAAI;AAChC;","names":["resolve","resolve","resolve","resolve"]}
1
+ {"version":3,"sources":["../../@wippy-fe--shared/src/attention.ts","../../@wippy-fe--shared/src/attention-inspection.ts","../../@wippy-fe--shared/src/attention-relay-codec.ts","../../@wippy-fe--shared/src/surface.ts","../../@wippy-fe--shared/src/visibility.ts","../../@wippy-fe--shared/src/index.ts","../src/page-plugin.ts","../src/shared.ts","../src/component-plugin.ts","../src/index.ts"],"sourcesContent":["export const CONTEXT_ATTACHMENTS_MAX_COUNT = 8\nexport const CONTEXT_ATTACHMENTS_MAX_BYTES = 32 * 1024\nexport const CONTEXT_ATTACHMENT_MAX_BYTES = 32 * 1024\nexport const ATTENTION_CONTEXT_KIND = 'wippy.attention'\nexport const ATTENTION_CONTEXT_VERSION = 1\nexport const ATTENTION_CONTEXT_SCHEMA = 'wippy.attention.v1'\nexport const ATTENTION_CONTEXT_V2_VERSION = 2\nexport const ATTENTION_CONTEXT_V2_SCHEMA = 'wippy.attention.v2'\nexport const ATTENTION_CONTEXT_V3_VERSION = 3\nexport const ATTENTION_CONTEXT_V3_SCHEMA = 'wippy.attention.v3'\nexport const ATTENTION_CONTEXT_V4_VERSION = 4\nexport const ATTENTION_CONTEXT_V4_SCHEMA = 'wippy.attention.v4'\nexport const ATTENTION_V2_PREFERRED_CONTENT_BYTES = 12 * 1024\nexport const ATTENTION_V2_MAX_ENVELOPE_BYTES = 16 * 1024\nexport const ATTENTION_V3_MAX_ENVELOPE_BYTES = 16 * 1024\nexport const ATTENTION_MAX_EXPANDED_BYTES = 256 * 1024\nexport const ATTENTION_V2_MAX_DICTIONARY = 4128\nexport const ATTENTION_V2_MAX_MEMBERSHIPS = 16384\nexport const ATTENTION_RELAY_SCHEMA = 'wippy.attention.relay.v1'\nexport const ATTENTION_CAPABILITY_SCHEMA = 'wippy.attention.capability.v1'\nexport const ATTENTION_RUNTIME_SCHEMA = 'wippy.attention.runtime.v1'\nexport const ATTENTION_UI_ACTION_SCHEMA = 'wippy.ui-action.v1'\n/** Longest UI action lifetime the Session broker issues (`expires_at - created_at`). */\nexport const ATTENTION_UI_ACTION_MAX_TTL_MS = 120_000\nexport const ATTENTION_VISUAL_CONTEXT_KIND = 'wippy.attention.visual'\nexport const ATTENTION_VISUAL_CONTEXT_SCHEMA = 'wippy.attention.visual.v1'\nexport const ATTENTION_VISUAL_CONTEXT_VERSION = 1\nexport const ATTENTION_MAX_QUERY_POINTS = 4096\nexport const ATTENTION_MAX_CANDIDATES = 128\nexport const ATTENTION_MAX_PATH_SEGMENTS = 32\nexport const ATTENTION_MAX_POINT_OMISSIONS = 32\nexport const ATTENTION_MAX_SNAPSHOT_OMISSIONS = 128\nexport const ATTENTION_VISUAL_MAX_BYTES = 1024 * 1024\nexport const ATTENTION_VISUAL_MAX_HEIGHT_PX = 2048\nexport const ATTENTION_VISUAL_MAX_PIXELS = 4_194_304\nexport const ATTENTION_VISUAL_MAX_TIME_MS = 10_000\nexport const ATTENTION_VISUAL_MAX_WIDTH_PX = 2048\n\nexport type JsonPrimitive = boolean | number | string | null\nexport type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }\n\nexport interface AttentionConfig {\n enabled?: boolean\n messageContext?: {\n enabled?: boolean\n defaultInclude?: boolean\n }\n agentActions?: {\n enabled?: boolean\n requireConfirmation?: boolean\n }\n visualCapture?: {\n enabled?: boolean\n maxBytes?: number\n maxHeightPx?: number\n maxPixels?: number\n maxTimeMs?: number\n maxWidthPx?: number\n }\n sampling?: {\n radiusCssPx?: number\n stepCssPx?: number\n }\n privacy?: {\n text?: 'safe' | 'none'\n }\n}\n\nexport interface ContextAttachmentEnvelope {\n attachment_id: string\n kind: string\n version: number\n created_at: string\n expires_at?: string\n content_type: 'application/json'\n content_bytes: number\n content_hash: `sha256:${string}`\n content: string\n}\n\nexport interface ContextAttachmentInput<TPayload extends JsonValue> {\n attachment_id: string\n kind: string\n version: number\n created_at: string\n expires_at?: string\n payload: TPayload\n}\n\nexport type AttentionRealmKind\n = | 'host'\n | 'panel'\n | 'artifact'\n | 'page'\n | 'iframe'\n | 'web-fragment'\n | 'web-component'\n | 'shadow-root'\n | 'element'\n\nexport interface AttentionRect {\n x: number\n y: number\n width: number\n height: number\n}\n\nexport interface AttentionPoint {\n x: number\n y: number\n}\n\nexport interface AttentionQueryPoint extends AttentionPoint {\n point_id: string\n}\n\nexport interface AttentionTransform {\n matrix: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number]\n convention: 'dommatrix-column-major'\n direction: 'local-to-parent'\n}\n\nexport interface AttentionCoordinateSpace {\n kind: 'host-viewport'\n width: number\n height: number\n device_pixel_ratio: number\n}\n\nexport interface AttentionPathSegment {\n kind: AttentionRealmKind\n mount_id: string\n generation: number\n label?: string\n panel_id?: string\n surface_id?: string\n artifact_id?: string\n page_id?: string\n package_id?: string\n tag_name?: string\n selector_hint?: string\n frame_origin?: string\n rect?: AttentionRect\n clip_rect?: AttentionRect\n local_to_parent?: AttentionTransform\n coordinate_quality?: 'exact' | 'approximate'\n}\n\nexport interface AttentionTargetSummary {\n role?: string\n name?: string\n text?: string\n value?: string\n state?: Record<string, JsonPrimitive>\n}\n\nexport interface AttentionCandidate {\n target_id: string\n path: AttentionPathSegment[]\n rect: AttentionRect\n clip_rect?: AttentionRect\n sample_point_ids: string[]\n occluded: boolean\n summary: AttentionTargetSummary\n provenance?: {\n geometry_source: 'document' | 'physical-host'\n runtime_source: 'document' | 'iframe-realm' | 'fragment-realm'\n }\n action_ref?: AttentionTargetRefV1\n}\n\nexport interface AttentionObservedEvent {\n event_id: string\n sequence: number\n type: 'pointermove' | 'pointerdown' | 'pointerup' | 'click' | 'touchstart' | 'touchend'\n observed_at: string\n realm_time_ms: number\n point: AttentionPoint\n pointer_id?: number\n pointer_type?: 'mouse' | 'pen' | 'touch' | 'unknown'\n buttons?: number\n pointer_capture?: boolean\n candidate_ids: string[]\n}\n\nexport interface AttentionFocus {\n event_id: string\n sequence: number\n focused_at: string\n realm_time_ms: number\n candidate_id?: string\n path: AttentionPathSegment[]\n summary: AttentionTargetSummary\n}\n\nexport type AttentionSelectionUnknownReason = 'unavailable' | 'privacy' | 'detached' | 'unsupported'\n\nexport interface AttentionSelectionSelectedState {\n state: 'selected'\n origin_sequence: number\n origin_mount: AttentionMountRef\n selection: AttentionSelection\n}\n\nexport interface AttentionSelectionClearedState {\n state: 'cleared'\n origin_sequence: number\n origin_mount: AttentionMountRef\n reason: 'user-cleared' | 'detached' | 'navigation' | 'privacy' | 'disconnected' | 'revoked'\n cleared_at?: string\n}\n\nexport interface AttentionSelectionUnknownState {\n state: 'unknown'\n reason: AttentionSelectionUnknownReason\n}\n\nexport type AttentionSelectionState = AttentionSelectionSelectedState | AttentionSelectionClearedState | AttentionSelectionUnknownState\n\nexport interface AttentionSelectionRange {\n rect: AttentionRect\n coordinate_space: AttentionMountRef | 'host-viewport'\n}\n\nexport interface AttentionSelection {\n selection_id: string\n selected_at: string\n kind: 'text'\n collapsed: false\n direction: 'forward' | 'backward' | 'none'\n text: string\n anchor_path: AttentionPathSegment[]\n focus_path: AttentionPathSegment[]\n ranges: AttentionSelectionRange[]\n}\n\nexport function attentionSelectionSemanticKey(state: AttentionSelectionState): string {\n if (state.state !== 'selected')\n return JSON.stringify(state)\n const semanticPath = (path: AttentionPathSegment[]) => path.map(({\n rect: _rect,\n clip_rect: _clipRect,\n local_to_parent: _localToParent,\n coordinate_quality: _coordinateQuality,\n ...segment\n }) => segment)\n return JSON.stringify({\n state: state.state,\n origin_sequence: state.origin_sequence,\n origin_mount: state.origin_mount,\n selection_id: state.selection.selection_id,\n selected_at: state.selection.selected_at,\n kind: state.selection.kind,\n collapsed: state.selection.collapsed,\n direction: state.selection.direction,\n text: state.selection.text,\n anchor_path: semanticPath(state.selection.anchor_path),\n focus_path: semanticPath(state.selection.focus_path),\n })\n}\n\n/** Internal relay state. A departed realm cannot decide whether the Host focus is excluded. */\nexport interface AttentionFocusState {\n changed_at: string\n focus: AttentionFocus | null\n retained: boolean\n}\n\nexport interface AttentionObservationBatchV1 {\n received_at: string\n pointer?: AttentionObservedEvent\n focus?: AttentionFocus\n focus_state?: AttentionFocusState\n selection?: AttentionSelection\n recent_events: AttentionObservedEvent[]\n}\n\nexport type AttentionOmissionReason\n = | 'candidate-budget'\n | 'capability-unavailable'\n | 'child-disconnected'\n | 'child-timeout'\n | 'clipped'\n | 'depth-limit'\n | 'excluded'\n | 'navigation'\n | 'non-invertible-transform'\n | 'occluded'\n | 'point-budget'\n | 'query-budget'\n | 'redacted'\n | 'response-budget'\n | 'runtime-metadata-unavailable'\n | 'stale-mount'\n | 'unsupported-transform'\n | 'unsupported-boundary'\n | 'selection-unavailable'\n | 'selection-detached'\n | 'selection-excluded'\n | 'selection-redacted'\n\nexport interface AttentionOmission {\n reason: AttentionOmissionReason\n capture_code?: AttentionVisualCaptureOmissionCode\n point_id?: string\n mount_id?: string\n detail?: string\n}\n\nexport type AttentionVisualCaptureOmissionCode\n = | 'byte-limit'\n | 'capture-unavailable'\n | 'dimension-limit'\n | 'disabled'\n | 'encode-failed'\n | 'permission-denied'\n | 'pixel-limit'\n | 'privacy-excluded'\n | 'redaction-applied'\n | 'stale-target'\n | 'store-failed'\n | 'timeout'\n\nexport type AttentionCapabilityFeature\n = | 'observe-events'\n | 'query-points'\n | 'resolve-target'\n | 'highlight'\n | 'select'\n | 'capture-region'\n\nexport interface AttentionMountRef {\n mount_id: string\n generation: number\n}\n\nexport interface AttentionMountCoordinateSpace extends AttentionMountRef {\n kind: 'mount-viewport-css-px'\n}\n\nexport interface AttentionRuntimeBootstrapV1 {\n schema: typeof ATTENTION_RUNTIME_SCHEMA\n host_instance_id: string\n parent_origin: string\n target_origin: string\n parent_mount: AttentionMountRef\n target_mount: AttentionMountRef\n}\n\nexport interface AttentionLiveGeometry {\n coordinate_space: AttentionMountCoordinateSpace\n border_box: AttentionRect\n visible_clip?: AttentionRect\n local_to_parent?: AttentionTransform\n quality: 'exact' | 'approximate'\n}\n\nexport interface AttentionLiveQueryCandidateV1 {\n target_id: string\n path: AttentionPathSegment[]\n geometry: AttentionLiveGeometry\n sample_point_ids: string[]\n occluded: boolean\n summary: AttentionTargetSummary\n provenance?: AttentionCandidate['provenance']\n}\n\nexport interface AttentionCapabilityRequestV1 {\n schema: typeof ATTENTION_CAPABILITY_SCHEMA\n message_type: 'capability-request'\n request_id: string\n nonce: string\n host_instance_id: string\n parent_mount: AttentionMountRef\n target_mount: AttentionMountRef\n supported_versions: number[]\n}\n\nexport interface AttentionCapabilityGrantV1 {\n schema: typeof ATTENTION_CAPABILITY_SCHEMA\n message_type: 'capability-grant'\n request_id: string\n nonce: string\n host_instance_id: string\n parent_mount: AttentionMountRef\n target_mount: AttentionMountRef\n selected_version: 1 | 2\n capability_token: string\n issued_at: string\n expires_at: string\n features: AttentionCapabilityFeature[]\n coordinate_spaces: Array<'mount-viewport-css-px'>\n limits: {\n max_query_points: number\n max_candidates: number\n max_response_bytes: number\n }\n runtime?: {\n engine: 'iframe' | 'web-fragment' | 'web-component' | 'host'\n package_id?: string\n artifact_id?: string\n page_id?: string\n }\n}\n\nexport type AttentionCapabilityV1 = AttentionCapabilityRequestV1 | AttentionCapabilityGrantV1\n\nexport interface AttentionPointQueryV1 {\n schema: typeof ATTENTION_RELAY_SCHEMA\n message_type: 'query-request'\n request_id: string\n snapshot_id: string\n sequence: number\n host_instance_id: string\n parent_request_id?: string\n observation_scope?: 'focus'\n parent_mount: AttentionMountRef\n target_mount: AttentionMountRef\n capability_token: string\n created_at: string\n deadline_at: string\n coordinate_space: AttentionMountCoordinateSpace\n points: AttentionQueryPoint[]\n recursion: {\n depth: number\n max_depth: number\n }\n budget: AttentionQueryBudget\n}\n\nexport interface AttentionQueryBudget {\n remaining_depth: number\n remaining_queries: number\n remaining_query_points: number\n remaining_candidates: number\n remaining_bytes: number\n}\n\nexport type AttentionPointResultStatus = 'hit' | 'empty' | 'partial' | 'failed'\n\nexport interface AttentionPointResult {\n point_id: string\n status: AttentionPointResultStatus\n complete: boolean\n candidate_ids: string[]\n omissions: AttentionOmission[]\n}\n\nexport interface AttentionPointQueryResultV1 {\n schema: typeof ATTENTION_RELAY_SCHEMA\n message_type: 'query-result'\n request_id: string\n snapshot_id: string\n sequence: number\n host_instance_id: string\n source_mount: AttentionMountRef\n created_at: string\n point_results: AttentionPointResult[]\n candidates: AttentionLiveQueryCandidateV1[]\n observations?: AttentionObservationBatchV1\n omissions: AttentionOmission[]\n complete: boolean\n}\n\nexport type AttentionQueryErrorCode\n = | 'invalid-request'\n | 'unsupported-version'\n | 'unauthorized'\n | 'host-instance-mismatch'\n | 'capability-expired'\n | 'capability-revoked'\n | 'generation-mismatch'\n | 'deadline-exceeded'\n | 'detached'\n | 'navigation'\n | 'disconnected'\n | 'response-too-large'\n | 'internal-error'\n\nexport interface AttentionPointQueryErrorV1 {\n schema: typeof ATTENTION_RELAY_SCHEMA\n message_type: 'query-error'\n request_id: string\n snapshot_id: string\n sequence: number\n host_instance_id: string\n source_mount: AttentionMountRef\n created_at: string\n code: AttentionQueryErrorCode\n point_ids: string[]\n message?: string\n}\n\nexport interface AttentionSelectionUpdateV1 {\n schema: typeof ATTENTION_RELAY_SCHEMA\n message_type: 'selection-update'\n sequence: number\n slot_revision: number\n host_instance_id: string\n source_mount: AttentionMountRef\n parent_mount: AttentionMountRef\n capability_token: string\n observed_at: string\n selection_state: AttentionSelectionState\n}\n\nexport interface AttentionSelectionQueryRequestV1 {\n schema: typeof ATTENTION_RELAY_SCHEMA\n message_type: 'selection-query-request'\n request_id: string\n snapshot_id: string\n sequence: number\n host_instance_id: string\n parent_request_id?: string\n parent_mount: AttentionMountRef\n target_mount: AttentionMountRef\n capability_token: string\n created_at: string\n deadline_at: string\n remaining_depth: number\n remaining_queries: number\n remaining_bytes: number\n}\n\nexport interface AttentionSelectionQueryResultV1 {\n schema: typeof ATTENTION_RELAY_SCHEMA\n message_type: 'selection-query-result'\n request_id: string\n snapshot_id: string\n sequence: number\n host_instance_id: string\n source_mount: AttentionMountRef\n created_at: string\n slot_revision: number\n selection_state: AttentionSelectionState\n complete: boolean\n omissions: AttentionOmission[]\n}\n\nexport interface AttentionSelectionQueryErrorV1 {\n schema: typeof ATTENTION_RELAY_SCHEMA\n message_type: 'selection-query-error'\n request_id: string\n snapshot_id: string\n sequence: number\n host_instance_id: string\n source_mount: AttentionMountRef\n created_at: string\n code: AttentionQueryErrorCode\n message?: string\n}\n\nexport type AttentionRelayMessageV1 = AttentionPointQueryV1 | AttentionPointQueryResultV1 | AttentionPointQueryErrorV1 | AttentionSelectionUpdateV1 | AttentionSelectionQueryRequestV1 | AttentionSelectionQueryResultV1 | AttentionSelectionQueryErrorV1\n\nexport interface AttentionTargetRefV1 {\n snapshot_id: string\n target_id: string\n host_instance_id: string\n mount_id: string\n generation: number\n path_digest: `sha256:${string}`\n rect: AttentionRect\n label?: string\n}\n\nexport type AttentionUiActionMode = 'highlight' | 'confirm' | 'select' | 'capture_visual'\n\nexport type AttentionVisualCaptureScope = 'target' | 'region' | 'viewport'\n\nexport interface AttentionUiActionCaptureV1 {\n scope: AttentionVisualCaptureScope\n region?: AttentionRect\n allow_adjustment: boolean\n allow_viewport_choice: boolean\n format: 'image/png' | 'image/webp'\n}\n\nexport interface AttentionUiActionRequestV1 {\n schema: typeof ATTENTION_UI_ACTION_SCHEMA\n message_type: 'request'\n action_id: string\n request_id: string\n session_id: string\n host_instance_id: string\n created_at: string\n expires_at: string\n mode: AttentionUiActionMode\n prompt?: string\n targets: AttentionTargetRefV1[]\n allow_pointer: boolean\n allow_keyboard: boolean\n capture_region?: boolean\n capture?: AttentionUiActionCaptureV1\n}\n\nexport type AttentionUiActionStatus\n = | 'selected'\n | 'confirmed'\n | 'prepared'\n | 'cancelled'\n | 'denied'\n | 'rejected'\n | 'expired'\n | 'stale'\n | 'disconnected'\n | 'permission-denied'\n | 'unavailable'\n | 'error'\n\nexport interface AttentionPreparedFileV1 {\n uuid: string\n name: string\n mime_type: 'image/png' | 'image/webp'\n byte_size: number\n sha256: `sha256:${string}`\n scope: AttentionVisualCaptureScope\n}\n\nexport interface AttentionUiActionResultV1 {\n schema: typeof ATTENTION_UI_ACTION_SCHEMA\n message_type: 'result'\n result_id: string\n in_reply_to_action_id: string\n request_id: string\n session_id: string\n host_instance_id: string\n completed_at: string\n status: AttentionUiActionStatus\n selected_target?: AttentionTargetRefV1\n prepared_file?: AttentionPreparedFileV1\n reason?: string\n}\n\nexport type AttentionUiActionMessageV1 = AttentionUiActionRequestV1 | AttentionUiActionResultV1\n\nexport interface AttentionVisualContextV1 {\n schema: typeof ATTENTION_VISUAL_CONTEXT_SCHEMA\n capture_id: string\n snapshot_id: string\n host_instance_id: string\n created_at: string\n expires_at: string\n candidate_ids: string[]\n region: AttentionRect\n media: {\n content_type: 'image/png' | 'image/webp'\n content_bytes: number\n content_hash: `sha256:${string}`\n pixel_width: number\n pixel_height: number\n }\n reference: {\n kind: 'upload'\n opaque_id: string\n }\n authorization: {\n scope: 'session'\n session_id: string\n audience: 'agent-context'\n expires_at: string\n }\n redactions_applied: number\n}\n\nexport interface AttentionSnapshotV1 {\n schema: typeof ATTENTION_CONTEXT_SCHEMA\n snapshot_id: string\n host_instance_id: string\n mount_generation: number\n created_at: string\n coordinate_space: AttentionCoordinateSpace\n capture: {\n radius_css_px: number\n grid_step_css_px: number\n sampled_points: number\n points: AttentionQueryPoint[]\n duration_ms: number\n complete: boolean\n }\n selection?: AttentionSelection\n pointer?: AttentionObservedEvent\n focus?: AttentionFocus\n recent_events: AttentionObservedEvent[]\n candidates: AttentionCandidate[]\n omissions: AttentionOmission[]\n}\n\nexport type AttentionContextAttachment = ContextAttachmentEnvelope\n\nexport type AttentionSampleRefV2 = string | number | [number, number]\nexport interface AttentionGridEncodingV2 {\n kind: 'css-euclidean-grid.v1'\n origin: AttentionPoint\n overrides?: { index: number, point: AttentionQueryPoint }[]\n additional_points?: AttentionQueryPoint[]\n}\nexport interface AttentionCandidateV2 extends Omit<AttentionCandidate, 'path' | 'sample_point_ids'> {\n path_indices: number[]\n sample_refs: AttentionSampleRefV2[]\n}\nexport interface AttentionSnapshotV2 extends Omit<AttentionSnapshotV1, 'schema' | 'capture' | 'candidates' | 'focus'> {\n schema: typeof ATTENTION_CONTEXT_V2_SCHEMA\n path_dictionary: AttentionPathSegment[]\n candidates: AttentionCandidateV2[]\n focus?: Omit<AttentionFocus, 'path'> & { path_indices: number[] }\n capture: Omit<AttentionSnapshotV1['capture'], 'points'> & (\n { points: AttentionQueryPoint[], point_encoding?: never }\n | { point_encoding: AttentionGridEncodingV2, points?: never }\n )\n}\n\nexport type AttentionRectTupleV3 = [number, number, number, number]\nexport interface AttentionPathAttributesV3 extends Omit<AttentionPathSegment, 'kind' | 'mount_id' | 'generation' | 'rect' | 'clip_rect' | 'local_to_parent'> {\n rect?: AttentionRectTupleV3\n clip_rect?: AttentionRectTupleV3\n local_to_parent?: AttentionTransform['matrix']\n}\nexport type AttentionPathSegmentV3 = [AttentionRealmKind, string, number, AttentionPathAttributesV3]\nexport interface AttentionSnapshotV3 extends Omit<AttentionSnapshotV2, 'schema' | 'path_dictionary'> {\n schema: typeof ATTENTION_CONTEXT_V3_SCHEMA\n path_dictionary: AttentionPathSegmentV3[]\n}\n\nexport interface AttentionSelectionV4 extends Omit<AttentionSelection, 'anchor_path' | 'focus_path'> {\n anchor_path_indices: number[]\n focus_path_indices: number[]\n}\n\nexport interface AttentionSnapshotV4 extends Omit<AttentionSnapshotV3, 'schema' | 'selection'> {\n schema: typeof ATTENTION_CONTEXT_V4_SCHEMA\n selection?: AttentionSelectionV4\n}\n\nexport interface ContextAttachmentValidationOptions {\n now?: number\n allowExpired?: boolean\n maxCount?: number\n maxTotalBytes?: number\n maxAttachmentBytes?: number\n}\n\nexport interface ContextAttachmentValidationError {\n attachment_id?: string\n code:\n | 'array-required'\n | 'too-many-attachments'\n | 'total-bytes-exceeded'\n | 'invalid-envelope'\n | 'duplicate-attachment-id'\n | 'attachment-bytes-exceeded'\n | 'content-size-mismatch'\n | 'invalid-content-hash'\n | 'content-hash-mismatch'\n | 'action-ref-path-digest-mismatch'\n | 'noncanonical-content'\n | 'forbidden-live-field'\n | 'expired'\n | 'invalid-attention-payload'\n path: string\n message: string\n}\n\nexport type ContextAttachmentValidationResult\n = | { ok: true, attachments: ContextAttachmentEnvelope[] }\n | { ok: false, errors: ContextAttachmentValidationError[] }\n\nfunction hasOwn(value: object, key: PropertyKey): boolean {\n // eslint-disable-next-line e18e/prefer-object-has-own -- The shared package targets ES2020.\n return Object.prototype.hasOwnProperty.call(value, key)\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object' || Array.isArray(value))\n return false\n const prototype = Object.getPrototypeOf(value)\n if (prototype === null)\n return true\n const constructor = hasOwn(prototype, 'constructor')\n ? prototype.constructor\n : undefined\n return typeof constructor === 'function'\n && Function.prototype.toString.call(constructor) === Function.prototype.toString.call(Object)\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value)\n}\n\nfunction isNonEmptyString(value: unknown, maxLength: number): value is string {\n return typeof value === 'string' && value.length > 0 && value.length <= maxLength\n}\n\nfunction isOptionalString(value: unknown, maxLength: number): value is string | undefined {\n return value === undefined || (typeof value === 'string' && value.length <= maxLength)\n}\n\nfunction isIsoTimestamp(value: unknown): value is string {\n if (!isNonEmptyString(value, 64))\n return false\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.\\d{1,9})?Z$/.exec(value)\n if (!match)\n return false\n const [, yearText, monthText, dayText, hourText, minuteText, secondText] = match\n const year = Number(yearText)\n const month = Number(monthText)\n const day = Number(dayText)\n const hour = Number(hourText)\n const minute = Number(minuteText)\n const second = Number(secondText)\n const leapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)\n const daysInMonth = [31, leapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]\n return year >= 1\n && month >= 1 && month <= 12\n && day >= 1 && day <= daysInMonth[month - 1]\n && hour <= 23\n && minute <= 59\n && second <= 59\n && Number.isFinite(Date.parse(value))\n}\n\nfunction isFrameOrigin(value: unknown): value is string | undefined {\n if (value === undefined)\n return true\n if (value === 'about:srcdoc')\n return true\n if (typeof value !== 'string' || value.length > 2048)\n return false\n try {\n const parsed = new URL(value)\n return (parsed.protocol === 'http:' || parsed.protocol === 'https:')\n && parsed.origin === value\n && parsed.username === ''\n && parsed.password === ''\n && parsed.pathname === '/'\n && parsed.search === ''\n && parsed.hash === ''\n }\n catch {\n return false\n }\n}\n\nfunction isWebOrigin(value: unknown): value is string {\n return typeof value === 'string'\n && (value.startsWith('https://') || value.startsWith('http://'))\n && isFrameOrigin(value)\n}\n\nfunction isRect(value: unknown): value is AttentionRect {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['x', 'y', 'width', 'height'])\n && isFiniteNumber(value.x)\n && isFiniteNumber(value.y)\n && isFiniteNumber(value.width)\n && value.width >= 0\n && isFiniteNumber(value.height)\n && value.height >= 0\n}\n\nfunction isPoint(value: unknown): value is AttentionPoint {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['x', 'y'])\n && isFiniteNumber(value.x)\n && isFiniteNumber(value.y)\n}\n\nfunction isQueryPoint(value: unknown): value is AttentionQueryPoint {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['point_id', 'x', 'y'])\n && isNonEmptyString(value.point_id, 128)\n && isFiniteNumber(value.x)\n && isFiniteNumber(value.y)\n}\n\nfunction isPathSegment(value: unknown): value is AttentionPathSegment {\n if (!isPlainObject(value))\n return false\n const kinds: AttentionRealmKind[] = ['host', 'panel', 'artifact', 'page', 'iframe', 'web-fragment', 'web-component', 'shadow-root', 'element']\n return hasOnlyKeys(value, ['kind', 'mount_id', 'generation', 'label', 'panel_id', 'surface_id', 'artifact_id', 'page_id', 'package_id', 'tag_name', 'selector_hint', 'frame_origin', 'rect', 'clip_rect', 'local_to_parent', 'coordinate_quality'])\n && kinds.includes(value.kind as AttentionRealmKind)\n && isNonEmptyString(value.mount_id, 160)\n && Number.isSafeInteger(value.generation)\n && (value.generation as number) >= 0\n && isOptionalString(value.label, 256)\n && isOptionalString(value.panel_id, 128)\n && isOptionalString(value.surface_id, 128)\n && isOptionalString(value.artifact_id, 128)\n && isOptionalString(value.page_id, 128)\n && isOptionalString(value.package_id, 256)\n && isOptionalString(value.tag_name, 128)\n && isOptionalString(value.selector_hint, 512)\n && isFrameOrigin(value.frame_origin)\n && (value.rect === undefined || isRect(value.rect))\n && (value.clip_rect === undefined || isRect(value.clip_rect))\n && (value.local_to_parent === undefined || isTransform(value.local_to_parent))\n && (value.coordinate_quality === undefined || value.coordinate_quality === 'exact' || value.coordinate_quality === 'approximate')\n}\n\nexport function isAttentionStructuralPath(value: unknown): value is AttentionPathSegment[] {\n return Array.isArray(value) && value.length > 0 && value.length <= 32 && value.every(isPathSegment)\n}\n\nconst isPath = isAttentionStructuralPath\n\nfunction isSummary(value: unknown): value is AttentionTargetSummary {\n if (!isPlainObject(value))\n return false\n if (!hasOnlyKeys(value, ['role', 'name', 'text', 'value', 'state']))\n return false\n const stringsValid = ['role', 'name', 'text', 'value'].every((key) => {\n const member = value[key]\n return isOptionalString(member, key === 'text' ? 1024 : 256)\n })\n if (!stringsValid || (value.state !== undefined && !isPlainObject(value.state)))\n return false\n return value.state === undefined || Object.values(value.state).every(member => member === null\n || typeof member === 'boolean'\n || typeof member === 'string'\n || isFiniteNumber(member))\n}\n\nexport function isAttentionObservedEvent(value: unknown): value is AttentionObservedEvent {\n if (!isPlainObject(value))\n return false\n const types = ['pointermove', 'pointerdown', 'pointerup', 'click', 'touchstart', 'touchend']\n const pointerTypes = ['mouse', 'pen', 'touch', 'unknown']\n return hasOnlyKeys(value, ['event_id', 'sequence', 'type', 'observed_at', 'realm_time_ms', 'point', 'pointer_id', 'pointer_type', 'buttons', 'pointer_capture', 'candidate_ids'])\n && isNonEmptyString(value.event_id, 128)\n && Number.isSafeInteger(value.sequence) && (value.sequence as number) >= 0\n && types.includes(value.type as string)\n && isIsoTimestamp(value.observed_at)\n && isFiniteNumber(value.realm_time_ms) && (value.realm_time_ms as number) >= 0\n && isPoint(value.point)\n && (value.pointer_id === undefined || Number.isSafeInteger(value.pointer_id))\n && (value.pointer_type === undefined || pointerTypes.includes(value.pointer_type as string))\n && (value.buttons === undefined || (Number.isSafeInteger(value.buttons) && (value.buttons as number) >= 0))\n && (value.pointer_capture === undefined || typeof value.pointer_capture === 'boolean')\n && Array.isArray(value.candidate_ids)\n && value.candidate_ids.length <= 128\n && new Set(value.candidate_ids).size === value.candidate_ids.length\n && value.candidate_ids.every(id => isNonEmptyString(id, 128))\n}\n\nconst isObservedEvent = isAttentionObservedEvent\n\nexport function isAttentionFocus(value: unknown): value is AttentionFocus {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['event_id', 'sequence', 'focused_at', 'realm_time_ms', 'candidate_id', 'path', 'summary'])\n && isNonEmptyString(value.event_id, 128)\n && Number.isSafeInteger(value.sequence) && (value.sequence as number) >= 0\n && isIsoTimestamp(value.focused_at)\n && isFiniteNumber(value.realm_time_ms) && (value.realm_time_ms as number) >= 0\n && isOptionalString(value.candidate_id, 128)\n && isPath(value.path)\n && isSummary(value.summary)\n}\n\nexport function isAttentionFocusState(value: unknown): value is AttentionFocusState {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['changed_at', 'focus', 'retained'])\n && isIsoTimestamp(value.changed_at)\n && (value.focus === null || isAttentionFocus(value.focus))\n && typeof value.retained === 'boolean'\n && (value.focus !== null || value.retained === false)\n}\n\nexport function isAttentionSelection(value: unknown): value is AttentionSelection {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['selection_id', 'selected_at', 'kind', 'collapsed', 'direction', 'text', 'anchor_path', 'focus_path', 'ranges'])\n && isNonEmptyString(value.selection_id, 128)\n && isIsoTimestamp(value.selected_at)\n && value.kind === 'text'\n && value.collapsed === false\n && ['forward', 'backward', 'none'].includes(value.direction as string)\n && typeof value.text === 'string'\n && utf8ByteLength(value.text) <= 1024\n && isPath(value.anchor_path)\n && isPath(value.focus_path)\n && Array.isArray(value.ranges)\n && value.ranges.length <= 4\n && value.ranges.every(range => isPlainObject(range)\n && hasOnlyKeys(range, ['rect', 'coordinate_space'])\n && isRect(range.rect)\n && (range.coordinate_space === 'host-viewport' || isMountRef(range.coordinate_space)))\n}\n\nfunction isObservationBatch(value: unknown): value is AttentionObservationBatchV1 {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['received_at', 'pointer', 'focus', 'focus_state', 'selection', 'recent_events'])\n && isIsoTimestamp(value.received_at)\n && (value.pointer === undefined || isObservedEvent(value.pointer))\n && (value.focus === undefined || isAttentionFocus(value.focus))\n && (value.focus_state === undefined || isAttentionFocusState(value.focus_state))\n && (value.selection === undefined || isAttentionSelection(value.selection))\n && Array.isArray(value.recent_events)\n && value.recent_events.length <= 32\n && value.recent_events.every(isObservedEvent)\n && new Set(value.recent_events.map(event => event.event_id)).size === value.recent_events.length\n}\n\nfunction hasOnlyKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {\n const allowed = new Set(keys)\n return Object.keys(value).every(key => allowed.has(key))\n}\n\nfunction isMountRef(value: unknown): value is AttentionMountRef {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['mount_id', 'generation'])\n && isNonEmptyString(value.mount_id, 160)\n && Number.isSafeInteger(value.generation)\n && (value.generation as number) >= 0\n}\n\nfunction hasMountValues(value: Record<string, unknown>): boolean {\n return isNonEmptyString(value.mount_id, 160)\n && Number.isSafeInteger(value.generation)\n && (value.generation as number) >= 0\n}\n\nfunction sameMount(left: AttentionMountRef, right: AttentionMountRef): boolean {\n return left.mount_id === right.mount_id && left.generation === right.generation\n}\n\nfunction isQueryBudget(value: unknown): value is AttentionQueryBudget {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['remaining_depth', 'remaining_queries', 'remaining_query_points', 'remaining_candidates', 'remaining_bytes'])\n && Number.isSafeInteger(value.remaining_depth) && (value.remaining_depth as number) >= 0 && (value.remaining_depth as number) <= 16\n && Number.isSafeInteger(value.remaining_queries) && (value.remaining_queries as number) >= 0 && (value.remaining_queries as number) <= 128\n && Number.isSafeInteger(value.remaining_query_points) && (value.remaining_query_points as number) >= 0 && (value.remaining_query_points as number) <= 4096\n && Number.isSafeInteger(value.remaining_candidates) && (value.remaining_candidates as number) >= 0 && (value.remaining_candidates as number) <= ATTENTION_MAX_CANDIDATES\n && Number.isSafeInteger(value.remaining_bytes) && (value.remaining_bytes as number) >= 0 && (value.remaining_bytes as number) <= 1_048_576\n}\n\nfunction isOmission(value: unknown): value is AttentionOmission {\n if (!isPlainObject(value))\n return false\n const reasons: AttentionOmissionReason[] = [\n 'candidate-budget',\n 'capability-unavailable',\n 'child-disconnected',\n 'child-timeout',\n 'clipped',\n 'depth-limit',\n 'excluded',\n 'navigation',\n 'non-invertible-transform',\n 'occluded',\n 'point-budget',\n 'query-budget',\n 'redacted',\n 'response-budget',\n 'runtime-metadata-unavailable',\n 'stale-mount',\n 'unsupported-transform',\n 'unsupported-boundary',\n 'selection-unavailable',\n 'selection-detached',\n 'selection-excluded',\n 'selection-redacted',\n ]\n const captureCodes: AttentionVisualCaptureOmissionCode[] = [\n 'byte-limit',\n 'capture-unavailable',\n 'dimension-limit',\n 'disabled',\n 'encode-failed',\n 'permission-denied',\n 'pixel-limit',\n 'privacy-excluded',\n 'redaction-applied',\n 'stale-target',\n 'store-failed',\n 'timeout',\n ]\n return hasOnlyKeys(value, ['reason', 'capture_code', 'point_id', 'mount_id', 'detail'])\n && reasons.includes(value.reason as AttentionOmissionReason)\n && (value.capture_code === undefined || captureCodes.includes(value.capture_code as AttentionVisualCaptureOmissionCode))\n && (value.point_id === undefined || isNonEmptyString(value.point_id, 128))\n && (value.mount_id === undefined || isNonEmptyString(value.mount_id, 160))\n && (value.detail === undefined || isNonEmptyString(value.detail, 512))\n}\n\nfunction isPointResult(value: unknown): value is AttentionPointResult {\n if (!isPlainObject(value))\n return false\n const statuses: AttentionPointResultStatus[] = ['hit', 'empty', 'partial', 'failed']\n const shapeValid = hasOnlyKeys(value, ['point_id', 'status', 'complete', 'candidate_ids', 'omissions'])\n && isNonEmptyString(value.point_id, 128)\n && statuses.includes(value.status as AttentionPointResultStatus)\n && typeof value.complete === 'boolean'\n && Array.isArray(value.candidate_ids) && value.candidate_ids.length <= 128\n && new Set(value.candidate_ids).size === value.candidate_ids.length\n && value.candidate_ids.every(id => isNonEmptyString(id, 160))\n && Array.isArray(value.omissions) && value.omissions.length <= 32 && value.omissions.every(isOmission)\n if (!shapeValid)\n return false\n const pointResult = value as unknown as AttentionPointResult\n const partial = pointResult.omissions.length > 0\n return pointResult.complete !== partial\n && (!pointResult.complete || pointResult.status !== 'partial')\n && (pointResult.complete || pointResult.status === 'partial' || pointResult.status === 'failed')\n && (pointResult.status !== 'hit' || pointResult.candidate_ids.length > 0)\n && (pointResult.status !== 'empty' || pointResult.candidate_ids.length === 0)\n}\n\nfunction isTransform(value: unknown): value is AttentionTransform {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['matrix', 'convention', 'direction'])\n && Array.isArray(value.matrix) && value.matrix.length === 16 && value.matrix.every(isFiniteNumber)\n && value.convention === 'dommatrix-column-major'\n && value.direction === 'local-to-parent'\n}\n\nfunction isProvenance(value: unknown): value is NonNullable<AttentionCandidate['provenance']> {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['geometry_source', 'runtime_source'])\n && (value.geometry_source === 'document' || value.geometry_source === 'physical-host')\n && (value.runtime_source === 'document' || value.runtime_source === 'iframe-realm' || value.runtime_source === 'fragment-realm')\n}\n\nfunction isLiveCandidate(value: unknown): value is AttentionLiveQueryCandidateV1 {\n if (!isPlainObject(value) || !isPlainObject(value.geometry))\n return false\n const geometry = value.geometry\n return hasOnlyKeys(value, ['target_id', 'path', 'geometry', 'sample_point_ids', 'occluded', 'summary', 'provenance'])\n && isNonEmptyString(value.target_id, 160)\n && isPath(value.path)\n && hasOnlyKeys(geometry, ['coordinate_space', 'border_box', 'visible_clip', 'local_to_parent', 'quality'])\n && isPlainObject(geometry.coordinate_space)\n && geometry.coordinate_space.kind === 'mount-viewport-css-px'\n && hasOnlyKeys(geometry.coordinate_space, ['kind', 'mount_id', 'generation'])\n && hasMountValues(geometry.coordinate_space)\n && isRect(geometry.border_box)\n && (geometry.visible_clip === undefined || isRect(geometry.visible_clip))\n && (geometry.local_to_parent === undefined || isTransform(geometry.local_to_parent))\n && (geometry.quality === 'exact' || geometry.quality === 'approximate')\n && Array.isArray(value.sample_point_ids) && value.sample_point_ids.length >= 1 && value.sample_point_ids.length <= 4096\n && new Set(value.sample_point_ids).size === value.sample_point_ids.length\n && value.sample_point_ids.every(id => isNonEmptyString(id, 128))\n && typeof value.occluded === 'boolean'\n && isSummary(value.summary)\n && (value.provenance === undefined || isProvenance(value.provenance))\n}\n\nexport function isAttentionRuntimeBootstrapV1(value: unknown): value is AttentionRuntimeBootstrapV1 {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['schema', 'host_instance_id', 'parent_origin', 'target_origin', 'parent_mount', 'target_mount'])\n && value.schema === ATTENTION_RUNTIME_SCHEMA\n && isNonEmptyString(value.host_instance_id, 160)\n && isWebOrigin(value.parent_origin)\n && isWebOrigin(value.target_origin)\n && isMountRef(value.parent_mount)\n && isMountRef(value.target_mount)\n}\n\nexport function isAttentionCapabilityV1(value: unknown): value is AttentionCapabilityV1 {\n if (!isPlainObject(value) || value.schema !== ATTENTION_CAPABILITY_SCHEMA)\n return false\n const common = isNonEmptyString(value.request_id, 160)\n && isNonEmptyString(value.nonce, 512) && value.nonce.length >= 32\n && isNonEmptyString(value.host_instance_id, 160)\n && isMountRef(value.parent_mount)\n && isMountRef(value.target_mount)\n if (!common)\n return false\n if (value.message_type === 'capability-request') {\n return hasOnlyKeys(value, ['schema', 'message_type', 'request_id', 'nonce', 'host_instance_id', 'parent_mount', 'target_mount', 'supported_versions'])\n && Array.isArray(value.supported_versions) && value.supported_versions.length >= 1 && value.supported_versions.length <= 16\n && new Set(value.supported_versions).size === value.supported_versions.length\n && value.supported_versions.every(version => Number.isSafeInteger(version) && (version as number) >= 1)\n }\n if (value.message_type !== 'capability-grant')\n return false\n const features: AttentionCapabilityFeature[] = ['observe-events', 'query-points', 'resolve-target', 'highlight', 'select', 'capture-region']\n const runtimeEngines = ['iframe', 'web-fragment', 'web-component', 'host']\n const runtimeValid = value.runtime === undefined || (isPlainObject(value.runtime)\n && hasOnlyKeys(value.runtime, ['engine', 'package_id', 'artifact_id', 'page_id'])\n && runtimeEngines.includes(value.runtime.engine as string)\n && isOptionalString(value.runtime.package_id, 256)\n && isOptionalString(value.runtime.artifact_id, 128)\n && isOptionalString(value.runtime.page_id, 128))\n return hasOnlyKeys(value, ['schema', 'message_type', 'request_id', 'nonce', 'host_instance_id', 'parent_mount', 'target_mount', 'selected_version', 'capability_token', 'issued_at', 'expires_at', 'features', 'coordinate_spaces', 'limits', 'runtime'])\n && (value.selected_version === 1 || value.selected_version === 2)\n && isNonEmptyString(value.capability_token, 512) && value.capability_token.length >= 32\n && isIsoTimestamp(value.issued_at) && isIsoTimestamp(value.expires_at)\n && Array.isArray(value.features) && new Set(value.features).size === value.features.length && value.features.every(feature => features.includes(feature as AttentionCapabilityFeature))\n && Array.isArray(value.coordinate_spaces) && value.coordinate_spaces.length >= 1 && new Set(value.coordinate_spaces).size === value.coordinate_spaces.length && value.coordinate_spaces.every(space => space === 'mount-viewport-css-px')\n && isPlainObject(value.limits)\n && hasOnlyKeys(value.limits, ['max_query_points', 'max_candidates', 'max_response_bytes'])\n && Number.isSafeInteger(value.limits.max_query_points) && (value.limits.max_query_points as number) >= 1 && (value.limits.max_query_points as number) <= 4096\n && Number.isSafeInteger(value.limits.max_candidates) && (value.limits.max_candidates as number) >= 1 && (value.limits.max_candidates as number) <= 128\n && Number.isSafeInteger(value.limits.max_response_bytes) && (value.limits.max_response_bytes as number) >= 1024 && (value.limits.max_response_bytes as number) <= 1_048_576\n && runtimeValid\n}\n\nexport function isAttentionSelectionState(state: unknown): state is AttentionSelectionState {\n if (!isPlainObject(state) || typeof state.state !== 'string')\n return false\n if (state.state === 'unknown')\n return hasOnlyKeys(state, ['state', 'reason']) && ['unavailable', 'privacy', 'detached', 'unsupported'].includes(state.reason as string)\n if (state.state === 'selected') {\n return hasOnlyKeys(state, ['state', 'origin_sequence', 'origin_mount', 'selection'])\n && Number.isSafeInteger(state.origin_sequence) && (state.origin_sequence as number) >= 0\n && isMountRef(state.origin_mount)\n && isAttentionSelection(state.selection)\n }\n return state.state === 'cleared'\n && hasOnlyKeys(state, ['state', 'origin_sequence', 'origin_mount', 'reason', 'cleared_at'])\n && Number.isSafeInteger(state.origin_sequence) && (state.origin_sequence as number) >= 0\n && isMountRef(state.origin_mount)\n && (state.cleared_at === undefined || isIsoTimestamp(state.cleared_at))\n && ['user-cleared', 'detached', 'navigation', 'privacy', 'disconnected', 'revoked'].includes(state.reason as string)\n}\n\nexport function isAttentionRelayMessageV1(value: unknown): value is AttentionRelayMessageV1 {\n if (!isPlainObject(value) || value.schema !== ATTENTION_RELAY_SCHEMA)\n return false\n const selectionStateValid = isAttentionSelectionState\n if (value.message_type === 'selection-update') {\n return hasOnlyKeys(value, ['schema', 'message_type', 'sequence', 'slot_revision', 'host_instance_id', 'source_mount', 'parent_mount', 'capability_token', 'observed_at', 'selection_state'])\n && Number.isSafeInteger(value.sequence) && (value.sequence as number) >= 0\n && Number.isSafeInteger(value.slot_revision) && (value.slot_revision as number) >= 0\n && isNonEmptyString(value.host_instance_id, 160)\n && isMountRef(value.source_mount) && isMountRef(value.parent_mount)\n && isNonEmptyString(value.capability_token, 512) && value.capability_token.length >= 32\n && isIsoTimestamp(value.observed_at)\n && selectionStateValid(value.selection_state)\n }\n if (value.message_type === 'selection-query-request') {\n return hasOnlyKeys(value, ['schema', 'message_type', 'request_id', 'snapshot_id', 'sequence', 'host_instance_id', 'parent_request_id', 'parent_mount', 'target_mount', 'capability_token', 'created_at', 'deadline_at', 'remaining_depth', 'remaining_queries', 'remaining_bytes'])\n && (value.parent_request_id === undefined || isNonEmptyString(value.parent_request_id, 160))\n && isNonEmptyString(value.request_id, 160) && isNonEmptyString(value.snapshot_id, 160)\n && Number.isSafeInteger(value.sequence) && (value.sequence as number) >= 0\n && isNonEmptyString(value.host_instance_id, 160)\n && isMountRef(value.parent_mount) && isMountRef(value.target_mount)\n && isNonEmptyString(value.capability_token, 512) && value.capability_token.length >= 32\n && isIsoTimestamp(value.created_at) && isIsoTimestamp(value.deadline_at)\n && Number.isSafeInteger(value.remaining_depth) && (value.remaining_depth as number) >= 0 && (value.remaining_depth as number) <= 16\n && Number.isSafeInteger(value.remaining_queries) && (value.remaining_queries as number) >= 0 && (value.remaining_queries as number) <= 128\n && Number.isSafeInteger(value.remaining_bytes) && (value.remaining_bytes as number) >= 0 && (value.remaining_bytes as number) <= 1_048_576\n }\n if (value.message_type === 'selection-query-result') {\n return hasOnlyKeys(value, ['schema', 'message_type', 'request_id', 'snapshot_id', 'sequence', 'slot_revision', 'host_instance_id', 'source_mount', 'created_at', 'selection_state', 'complete', 'omissions'])\n && isNonEmptyString(value.request_id, 160) && isNonEmptyString(value.snapshot_id, 160)\n && Number.isSafeInteger(value.sequence) && (value.sequence as number) >= 0\n && Number.isSafeInteger(value.slot_revision) && (value.slot_revision as number) >= 0\n && isNonEmptyString(value.host_instance_id, 160) && isMountRef(value.source_mount)\n && isIsoTimestamp(value.created_at) && selectionStateValid(value.selection_state)\n && typeof value.complete === 'boolean'\n && Array.isArray(value.omissions) && value.omissions.length <= 128 && value.omissions.every(isOmission)\n }\n if (value.message_type === 'selection-query-error') {\n return hasOnlyKeys(value, ['schema', 'message_type', 'request_id', 'snapshot_id', 'sequence', 'host_instance_id', 'source_mount', 'created_at', 'code', 'message'])\n && isNonEmptyString(value.request_id, 160) && isNonEmptyString(value.snapshot_id, 160)\n && Number.isSafeInteger(value.sequence) && (value.sequence as number) >= 0\n && isNonEmptyString(value.host_instance_id, 160) && isMountRef(value.source_mount)\n && isIsoTimestamp(value.created_at)\n && ['invalid-request', 'unsupported-version', 'unauthorized', 'host-instance-mismatch', 'capability-expired', 'capability-revoked', 'generation-mismatch', 'deadline-exceeded', 'detached', 'navigation', 'disconnected', 'response-too-large', 'internal-error'].includes(value.code as string)\n && (value.message === undefined || isNonEmptyString(value.message, 512))\n }\n const common = isNonEmptyString(value.request_id, 160)\n && isNonEmptyString(value.snapshot_id, 160)\n && Number.isSafeInteger(value.sequence) && (value.sequence as number) >= 0\n && isNonEmptyString(value.host_instance_id, 160)\n && isIsoTimestamp(value.created_at)\n if (!common)\n return false\n if (value.message_type === 'query-request') {\n return hasOnlyKeys(value, ['schema', 'message_type', 'request_id', 'snapshot_id', 'sequence', 'host_instance_id', 'parent_request_id', 'observation_scope', 'parent_mount', 'target_mount', 'capability_token', 'created_at', 'deadline_at', 'coordinate_space', 'points', 'recursion', 'budget'])\n && (value.observation_scope === undefined || (value.observation_scope === 'focus' && Array.isArray(value.points) && value.points.length === 0))\n && (value.parent_request_id === undefined || isNonEmptyString(value.parent_request_id, 160))\n && isMountRef(value.parent_mount) && isMountRef(value.target_mount)\n && isNonEmptyString(value.capability_token, 512) && value.capability_token.length >= 32\n && isIsoTimestamp(value.deadline_at)\n && isPlainObject(value.coordinate_space) && value.coordinate_space.kind === 'mount-viewport-css-px'\n && hasOnlyKeys(value.coordinate_space, ['kind', 'mount_id', 'generation']) && hasMountValues(value.coordinate_space)\n && sameMount(value.coordinate_space as unknown as AttentionMountRef, value.target_mount as unknown as AttentionMountRef)\n && Array.isArray(value.points) && value.points.length <= 4096 && value.points.every(isQueryPoint)\n && new Set(value.points.map((point: AttentionQueryPoint) => point.point_id)).size === value.points.length\n && isPlainObject(value.recursion)\n && hasOnlyKeys(value.recursion, ['depth', 'max_depth'])\n && Number.isSafeInteger(value.recursion.depth) && (value.recursion.depth as number) >= 0 && (value.recursion.depth as number) <= 16\n && Number.isSafeInteger(value.recursion.max_depth) && (value.recursion.max_depth as number) >= 0 && (value.recursion.max_depth as number) <= 16\n && (value.recursion.depth as number) <= (value.recursion.max_depth as number)\n && isQueryBudget(value.budget)\n && (value.budget as AttentionQueryBudget).remaining_depth <= (value.recursion.max_depth as number) - (value.recursion.depth as number)\n }\n if (value.message_type === 'query-result') {\n const shapeValid = hasOnlyKeys(value, ['schema', 'message_type', 'request_id', 'snapshot_id', 'sequence', 'host_instance_id', 'source_mount', 'created_at', 'point_results', 'candidates', 'observations', 'omissions', 'complete'])\n && isMountRef(value.source_mount)\n && Array.isArray(value.point_results) && value.point_results.length <= 4096 && value.point_results.every(isPointResult)\n && Array.isArray(value.candidates) && value.candidates.length <= 128 && value.candidates.every(isLiveCandidate)\n && (value.observations === undefined || isObservationBatch(value.observations))\n && Array.isArray(value.omissions) && value.omissions.length <= 128 && value.omissions.every(isOmission)\n && typeof value.complete === 'boolean'\n if (!shapeValid)\n return false\n const queryResult = value as unknown as AttentionPointQueryResultV1\n const candidateIds = new Set(queryResult.candidates.map(candidate => candidate.target_id))\n return candidateIds.size === queryResult.candidates.length\n && new Set(queryResult.point_results.map(result => result.point_id)).size === queryResult.point_results.length\n && queryResult.point_results.every(result => result.candidate_ids.every(id => candidateIds.has(id)))\n && queryResult.complete === (queryResult.omissions.length === 0 && queryResult.point_results.every(result => result.complete))\n }\n if (value.message_type !== 'query-error')\n return false\n const codes: AttentionQueryErrorCode[] = ['invalid-request', 'unsupported-version', 'unauthorized', 'host-instance-mismatch', 'capability-expired', 'capability-revoked', 'generation-mismatch', 'deadline-exceeded', 'detached', 'navigation', 'disconnected', 'response-too-large', 'internal-error']\n return hasOnlyKeys(value, ['schema', 'message_type', 'request_id', 'snapshot_id', 'sequence', 'host_instance_id', 'source_mount', 'created_at', 'code', 'point_ids', 'message'])\n && isMountRef(value.source_mount)\n && codes.includes(value.code as AttentionQueryErrorCode)\n && Array.isArray(value.point_ids) && value.point_ids.length <= 4096 && value.point_ids.every(id => isNonEmptyString(id, 160))\n && (value.message === undefined || isNonEmptyString(value.message, 512))\n}\n\nfunction isTargetRef(value: unknown): value is AttentionTargetRefV1 {\n return isPlainObject(value)\n && hasOnlyKeys(value, ['snapshot_id', 'target_id', 'host_instance_id', 'mount_id', 'generation', 'path_digest', 'rect', 'label'])\n && isNonEmptyString(value.snapshot_id, 160)\n && isNonEmptyString(value.target_id, 160)\n && isNonEmptyString(value.host_instance_id, 160)\n && isNonEmptyString(value.mount_id, 160)\n && Number.isSafeInteger(value.generation) && (value.generation as number) >= 0\n && typeof value.path_digest === 'string' && /^sha256:[a-f0-9]{64}$/.test(value.path_digest)\n && isRect(value.rect)\n && isOptionalString(value.label, 256)\n}\n\nexport function isAttentionUiActionMessageV1(value: unknown): value is AttentionUiActionMessageV1 {\n if (!isPlainObject(value) || value.schema !== ATTENTION_UI_ACTION_SCHEMA)\n return false\n const common = isNonEmptyString(value.request_id, 160)\n && isNonEmptyString(value.session_id, 160)\n && isNonEmptyString(value.host_instance_id, 160)\n if (!common)\n return false\n if (value.message_type === 'request') {\n const modes: AttentionUiActionMode[] = ['highlight', 'confirm', 'select', 'capture_visual']\n const targets = Array.isArray(value.targets) ? value.targets : undefined\n const targetsValid = targets !== undefined && targets.length <= 32 && targets.every(isTargetRef)\n const zeroTargetSelection = targetsValid && targets.length === 0\n && value.mode === 'select' && value.capture_region === true\n const capture = isPlainObject(value.capture)\n && hasOnlyKeys(value.capture, ['scope', 'region', 'allow_adjustment', 'allow_viewport_choice', 'format'])\n && ['target', 'region', 'viewport'].includes(value.capture.scope as string)\n && (value.capture.region === undefined || isRect(value.capture.region))\n && (value.capture.scope !== 'region' || isRect(value.capture.region))\n && typeof value.capture.allow_adjustment === 'boolean'\n && typeof value.capture.allow_viewport_choice === 'boolean'\n && (value.capture.format === 'image/png' || value.capture.format === 'image/webp')\n const captureRequest = value.mode === 'capture_visual'\n const lifetime = Date.parse(value.expires_at as string) - Date.parse(value.created_at as string)\n return hasOnlyKeys(value, ['schema', 'message_type', 'action_id', 'request_id', 'session_id', 'host_instance_id', 'created_at', 'expires_at', 'mode', 'prompt', 'targets', 'allow_pointer', 'allow_keyboard', 'capture_region', 'capture'])\n && isNonEmptyString(value.action_id, 160)\n && isIsoTimestamp(value.created_at)\n && isIsoTimestamp(value.expires_at)\n // The Host times a request from its receipt, so the lifetime must be\n // positive and within the broker's maximum.\n && lifetime > 0 && lifetime <= ATTENTION_UI_ACTION_MAX_TTL_MS\n && modes.includes(value.mode as AttentionUiActionMode)\n && isOptionalString(value.prompt, 512)\n && targetsValid\n && (targets.length > 0 || zeroTargetSelection)\n && typeof value.allow_pointer === 'boolean'\n && typeof value.allow_keyboard === 'boolean'\n && (captureRequest\n ? capture && value.capture_region === undefined\n : typeof value.capture_region === 'boolean' && value.capture === undefined)\n }\n if (value.message_type !== 'result')\n return false\n const statuses: AttentionUiActionStatus[] = ['selected', 'confirmed', 'prepared', 'cancelled', 'denied', 'rejected', 'expired', 'stale', 'disconnected', 'permission-denied', 'unavailable', 'error']\n if (!hasOnlyKeys(value, ['schema', 'message_type', 'result_id', 'in_reply_to_action_id', 'request_id', 'session_id', 'host_instance_id', 'completed_at', 'status', 'selected_target', 'prepared_file', 'reason'])\n || !isNonEmptyString(value.result_id, 160)\n || !isNonEmptyString(value.in_reply_to_action_id, 160)\n || !isIsoTimestamp(value.completed_at)\n || !statuses.includes(value.status as AttentionUiActionStatus)\n || (value.reason !== undefined && !isOptionalString(value.reason, 512))) {\n return false\n }\n const selected = value.status === 'selected' || value.status === 'confirmed'\n const prepared = value.status === 'prepared'\n const preparedFile = value.prepared_file\n const preparedFileValid = isPlainObject(preparedFile)\n && hasOnlyKeys(preparedFile, ['uuid', 'name', 'mime_type', 'byte_size', 'sha256', 'scope'])\n && isNonEmptyString(preparedFile.uuid, 160)\n && isNonEmptyString(preparedFile.name, 256)\n && (preparedFile.mime_type === 'image/png' || preparedFile.mime_type === 'image/webp')\n && Number.isSafeInteger(preparedFile.byte_size) && (preparedFile.byte_size as number) > 0 && (preparedFile.byte_size as number) <= ATTENTION_VISUAL_MAX_BYTES\n && typeof preparedFile.sha256 === 'string' && /^sha256:[a-f0-9]{64}$/.test(preparedFile.sha256)\n && ['target', 'region', 'viewport'].includes(preparedFile.scope as string)\n if (selected)\n return isTargetRef(value.selected_target) && value.prepared_file === undefined\n if (prepared)\n return value.selected_target === undefined && preparedFileValid\n return value.selected_target === undefined && value.prepared_file === undefined\n}\n\nexport function isAttentionVisualContextV1(value: unknown): value is AttentionVisualContextV1 {\n if (!isPlainObject(value)\n || !hasOnlyKeys(value, ['schema', 'capture_id', 'snapshot_id', 'host_instance_id', 'created_at', 'expires_at', 'candidate_ids', 'region', 'media', 'reference', 'authorization', 'redactions_applied'])\n || value.schema !== ATTENTION_VISUAL_CONTEXT_SCHEMA\n || !isNonEmptyString(value.capture_id, 160)\n || !isNonEmptyString(value.snapshot_id, 160)\n || !isNonEmptyString(value.host_instance_id, 160)\n || !isIsoTimestamp(value.created_at)\n || !isIsoTimestamp(value.expires_at)\n || !Array.isArray(value.candidate_ids)\n || value.candidate_ids.length === 0\n || value.candidate_ids.length > ATTENTION_MAX_CANDIDATES\n || new Set(value.candidate_ids).size !== value.candidate_ids.length\n || !value.candidate_ids.every(id => isNonEmptyString(id, 160))\n || !isRect(value.region)\n || !Number.isSafeInteger(value.redactions_applied)\n || (value.redactions_applied as number) < 0\n || (value.redactions_applied as number) > 4096) {\n return false\n }\n if (!isPlainObject(value.media)\n || !hasOnlyKeys(value.media, ['content_type', 'content_bytes', 'content_hash', 'pixel_width', 'pixel_height'])\n || (value.media.content_type !== 'image/png' && value.media.content_type !== 'image/webp')\n || !Number.isSafeInteger(value.media.content_bytes)\n || (value.media.content_bytes as number) < 1\n || (value.media.content_bytes as number) > ATTENTION_VISUAL_MAX_BYTES\n || typeof value.media.content_hash !== 'string'\n || !/^sha256:[a-f0-9]{64}$/.test(value.media.content_hash)\n || !Number.isSafeInteger(value.media.pixel_width)\n || (value.media.pixel_width as number) < 1\n || (value.media.pixel_width as number) > ATTENTION_VISUAL_MAX_WIDTH_PX\n || !Number.isSafeInteger(value.media.pixel_height)\n || (value.media.pixel_height as number) < 1\n || (value.media.pixel_height as number) > ATTENTION_VISUAL_MAX_HEIGHT_PX\n || (value.media.pixel_width as number) * (value.media.pixel_height as number) > ATTENTION_VISUAL_MAX_PIXELS) {\n return false\n }\n return isPlainObject(value.reference)\n && hasOnlyKeys(value.reference, ['kind', 'opaque_id'])\n && value.reference.kind === 'upload'\n && isNonEmptyString(value.reference.opaque_id, 160)\n && isPlainObject(value.authorization)\n && hasOnlyKeys(value.authorization, ['scope', 'session_id', 'audience', 'expires_at'])\n && value.authorization.scope === 'session'\n && isNonEmptyString(value.authorization.session_id, 160)\n && value.authorization.audience === 'agent-context'\n && isIsoTimestamp(value.authorization.expires_at)\n && value.authorization.expires_at === value.expires_at\n}\n\nexport function matchesAttentionQueryResult(\n request: AttentionPointQueryV1,\n result: AttentionPointQueryResultV1 | AttentionPointQueryErrorV1,\n onMismatch?: (reason: string) => void,\n): boolean {\n const mismatch = (reason: string): false => {\n onMismatch?.(reason)\n return false\n }\n if (request.request_id !== result.request_id\n || request.snapshot_id !== result.snapshot_id\n || request.sequence !== result.sequence\n || request.host_instance_id !== result.host_instance_id\n || !sameMount(request.target_mount, result.source_mount)) {\n return mismatch('identity')\n }\n const pointIds = new Set(request.points.map(point => point.point_id))\n if (result.message_type === 'query-error') {\n return new Set(result.point_ids).size === result.point_ids.length\n && result.point_ids.every(pointId => pointIds.has(pointId))\n }\n\n const observationPointIds = new Set([\n ...(result.observations?.pointer ? [result.observations.pointer.event_id] : []),\n ...(result.observations?.focus ? [result.observations.focus.event_id] : []),\n ...(result.observations?.focus_state?.focus ? [result.observations.focus_state.focus.event_id] : []),\n ...(result.observations?.recent_events.map(event => event.event_id) ?? []),\n ])\n const candidateIds = new Set<string>()\n for (const candidate of result.candidates) {\n if (candidateIds.has(candidate.target_id)\n || candidate.geometry.coordinate_space.mount_id !== result.source_mount.mount_id\n || candidate.geometry.coordinate_space.generation !== result.source_mount.generation\n || candidate.sample_point_ids.length === 0\n || new Set(candidate.sample_point_ids).size !== candidate.sample_point_ids.length\n || !candidate.sample_point_ids.every(pointId => pointIds.has(pointId) || observationPointIds.has(pointId))) {\n return mismatch('candidate-samples')\n }\n candidateIds.add(candidate.target_id)\n }\n\n if (result.point_results.length !== request.points.length\n || !result.point_results.every(pointResult => pointIds.has(pointResult.point_id))\n || new Set(result.point_results.map(pointResult => pointResult.point_id)).size !== request.points.length) {\n return mismatch('point-set')\n }\n\n const pointsByCandidate = new Map<string, Set<string>>()\n for (const pointResult of result.point_results) {\n if (!pointResult.candidate_ids.every(candidateId => candidateIds.has(candidateId)))\n return mismatch('point-candidate-reference')\n const partial = pointResult.omissions.length > 0\n if (pointResult.complete === partial\n || (pointResult.complete && pointResult.status === 'partial')\n || (!pointResult.complete && pointResult.status !== 'partial' && pointResult.status !== 'failed')\n || (pointResult.status === 'hit' && pointResult.candidate_ids.length === 0)\n || (pointResult.status === 'empty' && pointResult.candidate_ids.length > 0)) {\n return mismatch('point-state')\n }\n for (const candidateId of pointResult.candidate_ids) {\n const mapped = pointsByCandidate.get(candidateId) ?? new Set<string>()\n mapped.add(pointResult.point_id)\n pointsByCandidate.set(candidateId, mapped)\n }\n }\n\n const addObservationCandidate = (candidateId: string, eventId: string) => {\n const mapped = pointsByCandidate.get(candidateId) ?? new Set<string>()\n mapped.add(eventId)\n pointsByCandidate.set(candidateId, mapped)\n }\n if (result.observations?.pointer) {\n for (const candidateId of result.observations.pointer.candidate_ids) {\n if (!candidateIds.has(candidateId))\n return mismatch('pointer-candidate-reference')\n addObservationCandidate(candidateId, result.observations.pointer.event_id)\n }\n }\n if (result.observations?.focus?.candidate_id) {\n if (!candidateIds.has(result.observations.focus.candidate_id))\n return mismatch('focus-candidate-reference')\n addObservationCandidate(result.observations.focus.candidate_id, result.observations.focus.event_id)\n }\n if (result.observations?.focus_state?.focus?.candidate_id) {\n const retained = result.observations.focus_state.focus\n if (!candidateIds.has(retained.candidate_id!))\n return mismatch('focus-state-candidate-reference')\n addObservationCandidate(retained.candidate_id!, retained.event_id)\n }\n for (const event of result.observations?.recent_events ?? []) {\n for (const candidateId of event.candidate_ids) {\n if (!candidateIds.has(candidateId))\n return mismatch('recent-event-candidate-reference')\n addObservationCandidate(candidateId, event.event_id)\n }\n }\n\n for (const candidate of result.candidates) {\n const mapped = pointsByCandidate.get(candidate.target_id)\n if (!mapped || mapped.size !== candidate.sample_point_ids.length || candidate.sample_point_ids.some(pointId => !mapped.has(pointId)))\n return mismatch('candidate-sample-relation')\n }\n\n const complete = result.omissions.length === 0 && result.point_results.every(pointResult => pointResult.complete)\n return result.complete === complete || mismatch('result-completeness')\n}\n\nexport function matchesAttentionSelectionQueryResult(request: AttentionSelectionQueryRequestV1, result: AttentionSelectionQueryResultV1 | AttentionSelectionQueryErrorV1): boolean {\n return request.request_id === result.request_id\n && request.snapshot_id === result.snapshot_id\n && request.sequence === result.sequence\n && request.host_instance_id === result.host_instance_id\n && sameMount(request.target_mount, result.source_mount)\n && (result.message_type === 'selection-query-error' || result.complete === (result.omissions.length === 0))\n}\n\nexport function isAttentionSnapshotV1(value: unknown): value is AttentionSnapshotV1 {\n if (!isPlainObject(value) || value.schema !== ATTENTION_CONTEXT_SCHEMA)\n return false\n if (!hasOnlyKeys(value, ['schema', 'snapshot_id', 'host_instance_id', 'mount_generation', 'created_at', 'coordinate_space', 'capture', 'pointer', 'focus', 'selection', 'recent_events', 'candidates', 'omissions']))\n return false\n if (!isNonEmptyString(value.snapshot_id, 128) || !isNonEmptyString(value.host_instance_id, 160))\n return false\n if (!Number.isSafeInteger(value.mount_generation) || (value.mount_generation as number) < 0 || !isIsoTimestamp(value.created_at))\n return false\n if (!isPlainObject(value.coordinate_space)\n || !hasOnlyKeys(value.coordinate_space, ['kind', 'width', 'height', 'device_pixel_ratio'])\n || value.coordinate_space.kind !== 'host-viewport'\n || !isFiniteNumber(value.coordinate_space.width)\n || value.coordinate_space.width < 0\n || !isFiniteNumber(value.coordinate_space.height)\n || value.coordinate_space.height < 0\n || !isFiniteNumber(value.coordinate_space.device_pixel_ratio)\n || value.coordinate_space.device_pixel_ratio <= 0) {\n return false\n }\n if (!isPlainObject(value.capture)\n || !hasOnlyKeys(value.capture, ['radius_css_px', 'grid_step_css_px', 'sampled_points', 'points', 'duration_ms', 'complete'])\n || !isFiniteNumber(value.capture.radius_css_px)\n || value.capture.radius_css_px < 0 || value.capture.radius_css_px > 100\n || !isFiniteNumber(value.capture.grid_step_css_px)\n || value.capture.grid_step_css_px <= 0 || value.capture.grid_step_css_px > 100\n || !Number.isSafeInteger(value.capture.sampled_points)\n || (value.capture.sampled_points as number) < 0 || (value.capture.sampled_points as number) > 4096\n || !Array.isArray(value.capture.points)\n || value.capture.points.length !== value.capture.sampled_points\n || value.capture.points.length > 4096\n || !value.capture.points.every(isQueryPoint)\n || !isFiniteNumber(value.capture.duration_ms)\n || value.capture.duration_ms < 0\n || typeof value.capture.complete !== 'boolean') {\n return false\n }\n const pointIds = new Set<string>()\n for (const point of value.capture.points as AttentionQueryPoint[]) {\n if (pointIds.has(point.point_id))\n return false\n pointIds.add(point.point_id)\n }\n if (value.pointer !== undefined && !isObservedEvent(value.pointer))\n return false\n if (value.focus !== undefined && !isAttentionFocus(value.focus))\n return false\n if (value.selection !== undefined && !isAttentionSelection(value.selection))\n return false\n if (!Array.isArray(value.recent_events) || value.recent_events.length > 32 || !value.recent_events.every(isObservedEvent))\n return false\n const observationPointIds = new Set([\n ...(value.pointer ? [value.pointer.event_id] : []),\n ...(value.focus ? [value.focus.event_id] : []),\n ...value.recent_events.map(event => event.event_id),\n ])\n if (!Array.isArray(value.candidates) || value.candidates.length > 128)\n return false\n for (const candidate of value.candidates) {\n if (!isPlainObject(candidate)\n || !hasOnlyKeys(candidate, ['target_id', 'path', 'rect', 'clip_rect', 'sample_point_ids', 'occluded', 'summary', 'provenance', 'action_ref'])\n || !isNonEmptyString(candidate.target_id, 128)\n || !isPath(candidate.path)\n || !isRect(candidate.rect)\n || (candidate.clip_rect !== undefined && !isRect(candidate.clip_rect))\n || !Array.isArray(candidate.sample_point_ids)\n || candidate.sample_point_ids.length === 0\n || candidate.sample_point_ids.length > 4096\n || new Set(candidate.sample_point_ids).size !== candidate.sample_point_ids.length\n || !candidate.sample_point_ids.every(id => isNonEmptyString(id, 128) && (pointIds.has(id) || observationPointIds.has(id)))\n || typeof candidate.occluded !== 'boolean'\n || !isSummary(candidate.summary)\n || (candidate.provenance !== undefined && !isProvenance(candidate.provenance))) {\n return false\n }\n if (candidate.action_ref !== undefined) {\n const deepest = candidate.path[candidate.path.length - 1]\n if (!isTargetRef(candidate.action_ref)\n || candidate.action_ref.snapshot_id !== value.snapshot_id\n || candidate.action_ref.target_id !== candidate.target_id\n || candidate.action_ref.host_instance_id !== value.host_instance_id\n || candidate.action_ref.mount_id !== deepest.mount_id\n || candidate.action_ref.generation !== deepest.generation\n || candidate.action_ref.rect.x !== candidate.rect.x\n || candidate.action_ref.rect.y !== candidate.rect.y\n || candidate.action_ref.rect.width !== candidate.rect.width\n || candidate.action_ref.rect.height !== candidate.rect.height) {\n return false\n }\n }\n }\n const candidateIds = new Set(value.candidates.map(candidate => candidate.target_id))\n if (value.pointer?.candidate_ids.some(candidateId => !candidateIds.has(candidateId))\n || value.recent_events.some(event => event.candidate_ids.some(candidateId => !candidateIds.has(candidateId)))\n || (value.focus?.candidate_id !== undefined && !candidateIds.has(value.focus.candidate_id))) {\n return false\n }\n return Array.isArray(value.omissions) && value.omissions.length <= 128 && value.omissions.every(isOmission)\n}\n\nfunction compactAssert(condition: unknown): asserts condition {\n if (!condition)\n throw new RangeError('Invalid or excessive compact Attention context')\n}\n\nfunction compactJson(value: unknown): string {\n return canonicalJson(value as JsonValue)\n}\n\nfunction omitCompactKeys(value: Record<string, unknown>, excluded: string[]): Record<string, unknown> {\n return Object.fromEntries(Object.entries(value).filter(([key]) => !excluded.includes(key)))\n}\n\n/** Structural expansion only. Use the async integrity variant before trusting action digests. */\nexport function decodeAttentionSnapshotV2(value: unknown, maxExpandedBytes = ATTENTION_MAX_EXPANDED_BYTES): AttentionSnapshotV1 {\n assertJsonValue(value)\n compactAssert(isPlainObject(value) && value.schema === ATTENTION_CONTEXT_V2_SCHEMA\n && hasOnlyKeys(value, ['schema', 'snapshot_id', 'host_instance_id', 'mount_generation', 'created_at', 'coordinate_space', 'capture', 'pointer', 'focus', 'selection', 'recent_events', 'candidates', 'omissions', 'path_dictionary'])\n && Array.isArray(value.path_dictionary) && value.path_dictionary.length <= ATTENTION_V2_MAX_DICTIONARY\n && Array.isArray(value.candidates) && value.candidates.length <= ATTENTION_MAX_CANDIDATES\n && Array.isArray(value.recent_events) && value.recent_events.length <= 32\n && Array.isArray(value.omissions) && value.omissions.length <= ATTENTION_MAX_SNAPSHOT_OMISSIONS\n && (value.pointer === undefined || isObservedEvent(value.pointer))\n && (value.focus === undefined || isPlainObject(value.focus)))\n const dictionary: unknown[] = value.path_dictionary\n const recentEvents: unknown[] = value.recent_events\n compactAssert(dictionary.every(isPathSegment) && recentEvents.every(isObservedEvent))\n const segments = dictionary.map(compactJson)\n compactAssert(new Set(segments).size === segments.length)\n const capture = value.capture\n compactAssert(isPlainObject(capture)\n && hasOnlyKeys(capture, ['radius_css_px', 'grid_step_css_px', 'sampled_points', 'points', 'point_encoding', 'duration_ms', 'complete'])\n && isFiniteNumber(capture.radius_css_px) && capture.radius_css_px >= 0 && capture.radius_css_px <= 100\n && isFiniteNumber(capture.grid_step_css_px) && capture.grid_step_css_px >= 1 && capture.grid_step_css_px <= 100\n && Number.isSafeInteger(capture.sampled_points) && (capture.sampled_points as number) >= 0 && (capture.sampled_points as number) <= ATTENTION_MAX_QUERY_POINTS\n && (capture.points === undefined) !== (capture.point_encoding === undefined))\n const observationIds = new Set([\n ...(value.pointer ? [value.pointer.event_id] : []),\n ...(value.focus ? [value.focus.event_id] : []),\n ...recentEvents.map(event => event.event_id),\n ])\n const points: AttentionQueryPoint[] = []\n const candidates: AttentionCandidate[] = []\n const output = {\n ...omitCompactKeys(value, ['path_dictionary', 'capture', 'candidates', 'focus', 'selection']),\n schema: ATTENTION_CONTEXT_SCHEMA,\n capture: { ...omitCompactKeys(capture, ['point_encoding', 'points']), points },\n candidates,\n ...(value.focus ? { focus: { ...omitCompactKeys(value.focus, ['path_indices']), path: [] as AttentionPathSegment[] } } : {}),\n } as unknown as AttentionSnapshotV1\n compactAssert(!value.focus || value.focus.path === undefined)\n let used = utf8ByteLength(compactJson(output))\n const budget = Math.min(maxExpandedBytes, ATTENTION_MAX_EXPANDED_BYTES)\n compactAssert(Number.isSafeInteger(budget) && used <= budget)\n const charge = (bytes: number) => {\n compactAssert(bytes <= budget - used)\n used += bytes\n }\n const pointIds = new Set<string>()\n const appendPoint = (point: unknown) => {\n compactAssert(isQueryPoint(point) && !pointIds.has(point.point_id) && !observationIds.has(point.point_id) && points.length < ATTENTION_MAX_QUERY_POINTS)\n charge(utf8ByteLength(compactJson(point)) + (points.length ? 1 : 0))\n points.push(point)\n pointIds.add(point.point_id)\n }\n if (capture.points !== undefined) {\n compactAssert(Array.isArray(capture.points) && capture.points.length <= ATTENTION_MAX_QUERY_POINTS)\n capture.points.forEach(appendPoint)\n }\n else {\n const encoding = capture.point_encoding\n compactAssert(isPlainObject(encoding) && hasOnlyKeys(encoding, ['kind', 'origin', 'overrides', 'additional_points'])\n && encoding.kind === 'css-euclidean-grid.v1' && isPoint(encoding.origin)\n && (encoding.overrides === undefined || (Array.isArray(encoding.overrides) && encoding.overrides.length <= ATTENTION_MAX_QUERY_POINTS))\n && (encoding.additional_points === undefined || (Array.isArray(encoding.additional_points) && encoding.additional_points.length <= ATTENTION_MAX_QUERY_POINTS)))\n const extent = Math.floor(capture.radius_css_px / capture.grid_step_css_px)\n compactAssert((2 * extent + 1) ** 2 <= ATTENTION_MAX_QUERY_POINTS)\n const overrides = new Map<number, AttentionQueryPoint>()\n for (const override of encoding.overrides ?? []) {\n compactAssert(isPlainObject(override) && hasOnlyKeys(override, ['index', 'point'])\n && Number.isSafeInteger(override.index) && (override.index as number) >= 0\n && (override.index as number) < ATTENTION_MAX_QUERY_POINTS && !overrides.has(override.index as number) && isQueryPoint(override.point))\n overrides.set(override.index as number, override.point)\n }\n for (let yIndex = -extent; yIndex <= extent; yIndex++) {\n for (let xIndex = -extent; xIndex <= extent; xIndex++) {\n const x = xIndex * capture.grid_step_css_px\n const y = yIndex * capture.grid_step_css_px\n if (x * x + y * y <= capture.radius_css_px * capture.radius_css_px) {\n const index = points.length\n appendPoint(overrides.get(index) ?? { point_id: `p${index}`, x: encoding.origin.x + x, y: encoding.origin.y + y })\n overrides.delete(index)\n }\n }\n }\n compactAssert(overrides.size === 0)\n for (const point of encoding.additional_points ?? [])\n appendPoint(point)\n }\n compactAssert(points.length === capture.sampled_points)\n const seenSegments = new Set<number>()\n const path = (indices: unknown): AttentionPathSegment[] => {\n compactAssert(Array.isArray(indices) && indices.length > 0 && indices.length <= ATTENTION_MAX_PATH_SEGMENTS)\n const result: AttentionPathSegment[] = []\n for (const index of indices) {\n compactAssert(Number.isSafeInteger(index) && index >= 0 && index < dictionary.length)\n if (!seenSegments.has(index)) {\n compactAssert(index === seenSegments.size)\n seenSegments.add(index)\n }\n charge(utf8ByteLength(segments[index]) + (result.length ? 1 : 0))\n result.push(dictionary[index])\n }\n return result\n }\n let memberships = 0\n const targets = new Set<string>()\n for (const candidate of value.candidates) {\n compactAssert(isPlainObject(candidate) && hasOnlyKeys(candidate, ['target_id', 'path_indices', 'sample_refs', 'rect', 'clip_rect', 'occluded', 'summary', 'provenance', 'action_ref'])\n && isNonEmptyString(candidate.target_id, 128) && !targets.has(candidate.target_id)\n && Array.isArray(candidate.sample_refs) && candidate.sample_refs.length > 0 && candidate.sample_refs.length <= ATTENTION_MAX_QUERY_POINTS)\n const expanded = { ...omitCompactKeys(candidate, ['path_indices', 'sample_refs']), path: [] as AttentionPathSegment[], sample_point_ids: [] as string[] }\n charge(utf8ByteLength(compactJson(expanded)) + (candidates.length ? 1 : 0))\n expanded.path = path(candidate.path_indices)\n const seen = new Set<string>()\n const append = (id: string) => {\n compactAssert(!seen.has(id) && seen.size < ATTENTION_MAX_QUERY_POINTS && memberships < ATTENTION_V2_MAX_MEMBERSHIPS)\n charge(utf8ByteLength(JSON.stringify(id)) + (seen.size ? 1 : 0))\n expanded.sample_point_ids.push(id)\n seen.add(id)\n memberships++\n }\n for (const ref of candidate.sample_refs) {\n if (typeof ref === 'string') {\n compactAssert(observationIds.has(ref))\n append(ref)\n }\n else if (typeof ref === 'number') {\n compactAssert(Number.isSafeInteger(ref) && ref >= 0 && ref < points.length)\n append(points[ref].point_id)\n }\n else {\n compactAssert(Array.isArray(ref) && ref.length === 2)\n const [start, count] = ref\n compactAssert(isFiniteNumber(start) && Number.isSafeInteger(start) && start >= 0\n && isFiniteNumber(count) && Number.isSafeInteger(count) && count > 0 && start + count <= points.length\n && count <= ATTENTION_MAX_QUERY_POINTS - seen.size && count <= ATTENTION_V2_MAX_MEMBERSHIPS - memberships)\n for (let index = start; index < start + count; index++)\n append(points[index].point_id)\n }\n }\n candidates.push(expanded as unknown as AttentionCandidate)\n targets.add(candidate.target_id)\n }\n if (output.focus)\n output.focus.path = path(value.focus!.path_indices)\n if (value.selection !== undefined) {\n compactAssert(isPlainObject(value.selection))\n if (hasOwn(value.selection, 'anchor_path') || hasOwn(value.selection, 'focus_path')) {\n compactAssert(isAttentionSelection(value.selection))\n output.selection = value.selection\n }\n else {\n compactAssert(hasOnlyKeys(value.selection, ['selection_id', 'selected_at', 'kind', 'collapsed', 'direction', 'text', 'anchor_path_indices', 'focus_path_indices', 'ranges']))\n output.selection = {\n ...omitCompactKeys(value.selection, ['anchor_path_indices', 'focus_path_indices']),\n anchor_path: path(value.selection.anchor_path_indices),\n focus_path: path(value.selection.focus_path_indices),\n } as unknown as AttentionSelection\n }\n charge(utf8ByteLength(compactJson(output)) - used)\n }\n compactAssert(seenSegments.size === dictionary.length && isAttentionSnapshotV1(output) && !containsForbiddenContextKey(output))\n compactAssert(utf8ByteLength(compactJson(output)) === used)\n return JSON.parse(compactJson(output)) as AttentionSnapshotV1\n}\n\nexport function isAttentionSnapshotV2(value: unknown): value is AttentionSnapshotV2 {\n try {\n decodeAttentionSnapshotV2(value)\n return true\n }\n catch {\n return false\n }\n}\n\nexport async function decodeAttentionSnapshotV2Integrity(value: unknown, maxExpandedBytes = ATTENTION_MAX_EXPANDED_BYTES): Promise<AttentionSnapshotV1> {\n const snapshot = decodeAttentionSnapshotV2(value, maxExpandedBytes)\n for (const candidate of snapshot.candidates)\n compactAssert(!candidate.action_ref || await attentionPathDigestMatches(candidate.path, candidate.action_ref.path_digest))\n return snapshot\n}\n\n/** Deterministic compact encoding; a lattice is selected only after exact reconstruction. */\nexport async function encodeAttentionSnapshotV2(snapshot: AttentionSnapshotV1): Promise<AttentionSnapshotV2> {\n compactAssert(isAttentionSnapshotV1(snapshot) && !containsForbiddenContextKey(snapshot))\n const original = JSON.parse(JSON.stringify(snapshot)) as AttentionSnapshotV1\n const dictionary: AttentionPathSegment[] = []\n const indices = new Map<string, number>()\n const path = (segments: AttentionPathSegment[]) => segments.map((segment) => {\n const encoded = compactJson(segment)\n if (!indices.has(encoded)) {\n indices.set(encoded, dictionary.length)\n dictionary.push(segment)\n }\n return indices.get(encoded)!\n })\n const pointIndices = new Map(original.capture.points.map((point, index) => [point.point_id, index]))\n const candidates = original.candidates.map((candidate) => {\n const refs: AttentionSampleRefV2[] = []\n for (let index = 0; index < candidate.sample_point_ids.length;) {\n const id = candidate.sample_point_ids[index]\n const start = pointIndices.get(id)\n if (start === undefined) {\n refs.push(id)\n index++\n continue\n }\n let count = 1\n while (pointIndices.get(candidate.sample_point_ids[index + count]) === start + count)\n count++\n refs.push(count > 1 ? [start, count] : start)\n index += count\n }\n return { ...omitCompactKeys(candidate as unknown as Record<string, unknown>, ['path', 'sample_point_ids']), path_indices: path(candidate.path), sample_refs: refs } as AttentionCandidateV2\n })\n const focus = original.focus ? { ...omitCompactKeys(original.focus as unknown as Record<string, unknown>, ['path']), path_indices: path(original.focus.path) } as AttentionSnapshotV2['focus'] : undefined\n const capturePoints = original.capture.points\n let capture: AttentionSnapshotV2['capture'] = original.capture\n const origin = original.pointer?.point\n if (origin) {\n try {\n const grid = createAttentionSampleGrid(origin, capture.radius_css_px, capture.grid_step_css_px)\n if (grid.length <= capturePoints.length) {\n const overrides = grid.flatMap((point, index) => compactJson(point) === compactJson(capturePoints[index]) ? [] : [{ index, point: capturePoints[index] }])\n const additional = capturePoints.slice(grid.length)\n const point_encoding: AttentionGridEncodingV2 = { kind: 'css-euclidean-grid.v1', origin, ...(overrides.length ? { overrides } : {}), ...(additional.length ? { additional_points: additional } : {}) }\n const encodedCapture = { ...omitCompactKeys(capture as unknown as Record<string, unknown>, ['points']), point_encoding } as AttentionSnapshotV2['capture']\n if (utf8ByteLength(compactJson(encodedCapture)) < utf8ByteLength(compactJson(capture)))\n capture = encodedCapture\n }\n }\n catch {\n // An irregular or unrepresentable neighborhood keeps the explicit v1 points.\n }\n }\n const encoded = { ...original, schema: ATTENTION_CONTEXT_V2_SCHEMA, path_dictionary: dictionary, capture, candidates, ...(focus ? { focus } : {}) } as AttentionSnapshotV2\n const reconstructed = await decodeAttentionSnapshotV2Integrity(encoded)\n compactAssert(compactJson(reconstructed) === compactJson(original))\n return encoded\n}\n\nfunction packAttentionRectV3(rect: AttentionRect): AttentionRectTupleV3 {\n return [rect.x, rect.y, rect.width, rect.height]\n}\n\nfunction unpackAttentionRectV3(value: unknown): AttentionRect {\n compactAssert(Array.isArray(value) && value.length === 4\n && [0, 1, 2, 3].every(index => hasOwn(value, index))\n && value.every(isFiniteNumber)\n && value[2] >= 0 && value[3] >= 0)\n return { x: value[0], y: value[1], width: value[2], height: value[3] }\n}\n\nfunction packAttentionPathSegmentV3(segment: AttentionPathSegment): AttentionPathSegmentV3 {\n const attributes = omitCompactKeys(segment as unknown as Record<string, unknown>, [\n 'kind',\n 'mount_id',\n 'generation',\n 'rect',\n 'clip_rect',\n 'local_to_parent',\n ]) as unknown as AttentionPathAttributesV3\n if (segment.rect)\n attributes.rect = packAttentionRectV3(segment.rect)\n if (segment.clip_rect)\n attributes.clip_rect = packAttentionRectV3(segment.clip_rect)\n if (segment.local_to_parent)\n attributes.local_to_parent = [...segment.local_to_parent.matrix] as AttentionTransform['matrix']\n return [segment.kind, segment.mount_id, segment.generation, attributes]\n}\n\nfunction unpackAttentionPathSegmentV3(value: unknown): AttentionPathSegment {\n compactAssert(Array.isArray(value) && value.length === 4\n && [0, 1, 2, 3].every(index => hasOwn(value, index)))\n const [kind, mountId, generation, attributes] = value\n compactAssert(isPlainObject(attributes)\n && hasOnlyKeys(attributes, ['label', 'panel_id', 'surface_id', 'artifact_id', 'page_id', 'package_id', 'tag_name', 'selector_hint', 'frame_origin', 'rect', 'clip_rect', 'local_to_parent', 'coordinate_quality']))\n const matrix = attributes.local_to_parent\n compactAssert(matrix === undefined || (Array.isArray(matrix) && matrix.length === 16\n && Array.from({ length: 16 }, (_unused, index) => index).every(index => hasOwn(matrix, index))\n && matrix.every(isFiniteNumber)))\n const segment = {\n ...omitCompactKeys(attributes, ['rect', 'clip_rect', 'local_to_parent']),\n kind,\n mount_id: mountId,\n generation,\n ...(attributes.rect !== undefined ? { rect: unpackAttentionRectV3(attributes.rect) } : {}),\n ...(attributes.clip_rect !== undefined ? { clip_rect: unpackAttentionRectV3(attributes.clip_rect) } : {}),\n ...(matrix !== undefined\n ? { local_to_parent: { matrix, convention: 'dommatrix-column-major', direction: 'local-to-parent' } }\n : {}),\n }\n compactAssert(isPathSegment(segment))\n return segment\n}\n\n/** Expands the lossless v3 path tuples before all existing v2 structural and byte-budget validation. */\nexport function decodeAttentionSnapshotV3(value: unknown, maxExpandedBytes = ATTENTION_MAX_EXPANDED_BYTES): AttentionSnapshotV1 {\n assertJsonValue(value)\n compactAssert(isPlainObject(value) && value.schema === ATTENTION_CONTEXT_V3_SCHEMA\n && Array.isArray(value.path_dictionary) && value.path_dictionary.length <= ATTENTION_V2_MAX_DICTIONARY)\n const normalized = {\n ...value,\n schema: ATTENTION_CONTEXT_V2_SCHEMA,\n path_dictionary: value.path_dictionary.map(unpackAttentionPathSegmentV3),\n }\n return decodeAttentionSnapshotV2(normalized, maxExpandedBytes)\n}\n\nexport function isAttentionSnapshotV3(value: unknown): value is AttentionSnapshotV3 {\n try {\n decodeAttentionSnapshotV3(value)\n return true\n }\n catch {\n return false\n }\n}\n\nexport async function decodeAttentionSnapshotV3Integrity(value: unknown, maxExpandedBytes = ATTENTION_MAX_EXPANDED_BYTES): Promise<AttentionSnapshotV1> {\n const snapshot = decodeAttentionSnapshotV3(value, maxExpandedBytes)\n for (const candidate of snapshot.candidates)\n compactAssert(!candidate.action_ref || await attentionPathDigestMatches(candidate.path, candidate.action_ref.path_digest))\n return snapshot\n}\n\n/** Deterministic lossless v3 encoding with packed path-segment geometry. */\nexport async function encodeAttentionSnapshotV3(snapshot: AttentionSnapshotV1): Promise<AttentionSnapshotV3> {\n const v2 = await encodeAttentionSnapshotV2(snapshot)\n const encoded = {\n ...v2,\n schema: ATTENTION_CONTEXT_V3_SCHEMA,\n path_dictionary: v2.path_dictionary.map(packAttentionPathSegmentV3),\n } as AttentionSnapshotV3\n const reconstructed = await decodeAttentionSnapshotV3Integrity(encoded)\n compactAssert(compactJson(reconstructed) === compactJson(snapshot))\n return encoded\n}\n\nexport function decodeAttentionSnapshotV4(value: unknown, maxExpandedBytes = ATTENTION_MAX_EXPANDED_BYTES): AttentionSnapshotV1 {\n assertJsonValue(value)\n compactAssert(isPlainObject(value) && value.schema === ATTENTION_CONTEXT_V4_SCHEMA)\n return decodeAttentionSnapshotV3({ ...value, schema: ATTENTION_CONTEXT_V3_SCHEMA }, maxExpandedBytes)\n}\n\nexport function isAttentionSnapshotV4(value: unknown): value is AttentionSnapshotV4 {\n try {\n decodeAttentionSnapshotV4(value)\n return true\n }\n catch {\n return false\n }\n}\n\nexport async function decodeAttentionSnapshotV4Integrity(value: unknown, maxExpandedBytes = ATTENTION_MAX_EXPANDED_BYTES): Promise<AttentionSnapshotV1> {\n const snapshot = decodeAttentionSnapshotV4(value, maxExpandedBytes)\n for (const candidate of snapshot.candidates)\n compactAssert(!candidate.action_ref || await attentionPathDigestMatches(candidate.path, candidate.action_ref.path_digest))\n return snapshot\n}\n\nexport async function encodeAttentionSnapshotV4(snapshot: AttentionSnapshotV1): Promise<AttentionSnapshotV4> {\n const v3 = await encodeAttentionSnapshotV3(snapshot)\n const dictionary = [...v3.path_dictionary]\n const indices = new Map(dictionary.map((segment, index) => [compactJson(unpackAttentionPathSegmentV3(segment)), index]))\n const path = (segments: AttentionPathSegment[]) => segments.map((segment) => {\n const key = compactJson(segment)\n if (!indices.has(key)) {\n compactAssert(dictionary.length < ATTENTION_V2_MAX_DICTIONARY)\n indices.set(key, dictionary.length)\n dictionary.push(packAttentionPathSegmentV3(segment))\n }\n return indices.get(key)!\n })\n const selection = v3.selection\n ? {\n ...omitCompactKeys(v3.selection as unknown as Record<string, unknown>, ['anchor_path', 'focus_path']),\n anchor_path_indices: path(v3.selection.anchor_path),\n focus_path_indices: path(v3.selection.focus_path),\n } as unknown as AttentionSelectionV4\n : undefined\n const encoded = {\n ...omitCompactKeys(v3 as unknown as Record<string, unknown>, ['selection', 'path_dictionary']),\n schema: ATTENTION_CONTEXT_V4_SCHEMA,\n path_dictionary: dictionary,\n ...(selection ? { selection } : {}),\n } as unknown as AttentionSnapshotV4\n const reconstructed = await decodeAttentionSnapshotV4Integrity(encoded)\n compactAssert(compactJson(reconstructed) === compactJson(snapshot))\n return encoded\n}\n\nconst ATTENTION_FORBIDDEN_KEYS = new Set([\n 'capability_token',\n 'connection_target',\n 'delivery_handle',\n 'broker_pid',\n 'node_handle',\n 'reflection_handle',\n 'result_channel',\n 'runtime_context',\n 'signed_url',\n 'target_token',\n 'ui_action_runtime',\n])\n\nexport function containsForbiddenContextKey(value: unknown, allowVisualAuthorization = false): boolean {\n if (Array.isArray(value))\n return value.some(child => containsForbiddenContextKey(child, allowVisualAuthorization))\n if (!isPlainObject(value))\n return false\n return Object.entries(value).some(([key, child]) => {\n if (ATTENTION_FORBIDDEN_KEYS.has(key))\n return true\n if (key === 'authorization') {\n return !allowVisualAuthorization\n || !isPlainObject(child)\n || !hasOnlyKeys(child, ['scope', 'session_id', 'audience', 'expires_at'])\n || child.scope !== 'session'\n || !isNonEmptyString(child.session_id, 160)\n || child.audience !== 'agent-context'\n || !isIsoTimestamp(child.expires_at)\n }\n return containsForbiddenContextKey(child, allowVisualAuthorization)\n })\n}\n\nfunction compareUtf8(left: string, right: string): number {\n const leftBytes = new TextEncoder().encode(left)\n const rightBytes = new TextEncoder().encode(right)\n const length = Math.min(leftBytes.length, rightBytes.length)\n for (let index = 0; index < length; index++) {\n if (leftBytes[index] !== rightBytes[index])\n return leftBytes[index] - rightBytes[index]\n }\n return leftBytes.length - rightBytes.length\n}\n\nfunction assertJsonValue(value: unknown, depth = 0, seen = new Set<object>()): asserts value is JsonValue {\n if (depth > 32)\n throw new TypeError('Context attachment JSON exceeds the maximum depth of 32')\n if (value === null || typeof value === 'string' || typeof value === 'boolean')\n return\n if (typeof value === 'number') {\n if (!Number.isFinite(value))\n throw new TypeError('Context attachment JSON numbers must be finite')\n return\n }\n if (typeof value !== 'object')\n throw new TypeError('Context attachment content must contain only JSON values')\n if (seen.has(value))\n throw new TypeError('Context attachment JSON must not contain cycles')\n if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)\n throw new TypeError('Context attachment JSON objects must be plain objects')\n\n seen.add(value)\n if (Array.isArray(value)) {\n for (let index = 0; index < value.length; index++) {\n if (!(index in value))\n throw new TypeError('Context attachment JSON arrays must not be sparse')\n assertJsonValue(value[index], depth + 1, seen)\n }\n }\n else {\n for (const child of Object.values(value))\n assertJsonValue(child, depth + 1, seen)\n }\n seen.delete(value)\n}\n\nexport function canonicalJson(value: JsonValue): string {\n assertJsonValue(value)\n if (value === null || typeof value !== 'object')\n return JSON.stringify(value)\n if (Array.isArray(value))\n return `[${value.map(canonicalJson).join(',')}]`\n return `{${Object.keys(value).sort(compareUtf8).map(key => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`\n}\n\nexport function utf8ByteLength(value: string): number {\n return new TextEncoder().encode(value).byteLength\n}\n\nexport async function sha256Json(value: JsonValue): Promise<`sha256:${string}`> {\n return sha256Text(canonicalJson(value))\n}\n\n/** Stable path identity. Geometry is checked at request ingestion and may change while the same mounted target remains live. */\nexport function attentionSemanticPath(path: AttentionPathSegment[]): AttentionPathSegment[] {\n return path.map(({ rect: _rect, clip_rect: _clipRect, local_to_parent: _localToParent, coordinate_quality: _coordinateQuality, ...semantic }) => Object.fromEntries(\n Object.entries(semantic).filter(([, value]) => value !== undefined),\n ) as unknown as AttentionPathSegment)\n}\n\nexport async function sha256AttentionPath(path: AttentionPathSegment[]): Promise<`sha256:${string}`> {\n return sha256Json(attentionSemanticPath(path) as unknown as JsonValue)\n}\n\nasync function attentionPathDigestMatches(path: AttentionPathSegment[], digest: string): Promise<boolean> {\n if (await sha256AttentionPath(path) === digest)\n return true\n return await sha256Json(path as unknown as JsonValue) === digest\n}\n\nexport async function sha256Text(value: string): Promise<`sha256:${string}`> {\n const digest = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(value))\n const hex = Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join('')\n return `sha256:${hex}`\n}\n\nexport async function createContextAttachment<TPayload extends JsonValue>(\n fields: ContextAttachmentInput<TPayload>,\n): Promise<ContextAttachmentEnvelope> {\n const { payload, ...envelope } = fields\n const content = canonicalJson(payload)\n return {\n ...envelope,\n content_type: 'application/json',\n content_bytes: utf8ByteLength(content),\n content_hash: await sha256Text(content),\n content,\n }\n}\n\nexport function parseContextAttachmentContent<TPayload extends JsonValue = JsonValue>(attachment: ContextAttachmentEnvelope): TPayload {\n return JSON.parse(attachment.content) as TPayload\n}\n\nexport function validateContextAttachments(\n value: unknown,\n options: ContextAttachmentValidationOptions = {},\n): ContextAttachmentValidationResult {\n const errors: ContextAttachmentValidationError[] = []\n if (!Array.isArray(value)) {\n return { ok: false, errors: [{ code: 'array-required', path: 'context_attachments', message: 'context_attachments must be an array' }] }\n }\n const maxCount = Math.min(options.maxCount ?? CONTEXT_ATTACHMENTS_MAX_COUNT, CONTEXT_ATTACHMENTS_MAX_COUNT)\n const maxTotalBytes = Math.min(options.maxTotalBytes ?? CONTEXT_ATTACHMENTS_MAX_BYTES, CONTEXT_ATTACHMENTS_MAX_BYTES)\n const maxAttachmentBytes = Math.min(options.maxAttachmentBytes ?? CONTEXT_ATTACHMENT_MAX_BYTES, CONTEXT_ATTACHMENT_MAX_BYTES)\n const totalBytes = utf8ByteLength(JSON.stringify(value))\n if (value.length > maxCount)\n errors.push({ code: 'too-many-attachments', path: 'context_attachments', message: `at most ${maxCount} attachments are allowed` })\n if (totalBytes > maxTotalBytes)\n errors.push({ code: 'total-bytes-exceeded', path: 'context_attachments', message: `attachments exceed ${maxTotalBytes} bytes` })\n\n const ids = new Set<string>()\n let expandedAttentionBytes = 0\n value.forEach((candidate, index) => {\n const path = `context_attachments[${index}]`\n if (!isPlainObject(candidate)\n || !hasOnlyKeys(candidate, ['attachment_id', 'kind', 'version', 'created_at', 'expires_at', 'content_type', 'content_bytes', 'content_hash', 'content'])\n || !isNonEmptyString(candidate.attachment_id, 128)\n || !isNonEmptyString(candidate.kind, 128)\n || !/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/.test(candidate.kind as string)\n || !Number.isSafeInteger(candidate.version)\n || (candidate.version as number) < 1\n || !isIsoTimestamp(candidate.created_at)\n || (candidate.expires_at !== undefined && !isIsoTimestamp(candidate.expires_at))\n || candidate.content_type !== 'application/json'\n || !Number.isSafeInteger(candidate.content_bytes)\n || (candidate.content_bytes as number) < 0\n || (candidate.content_bytes as number) > CONTEXT_ATTACHMENT_MAX_BYTES\n || !isNonEmptyString(candidate.content_hash, 71)\n || typeof candidate.content !== 'string'\n || candidate.content.length > CONTEXT_ATTACHMENT_MAX_BYTES) {\n errors.push({ code: 'invalid-envelope', path, message: 'attachment envelope is invalid' })\n return\n }\n const attachmentId = candidate.attachment_id as string\n if (ids.has(attachmentId))\n errors.push({ attachment_id: attachmentId, code: 'duplicate-attachment-id', path: `${path}.attachment_id`, message: 'attachment_id must be unique within the message' })\n ids.add(attachmentId)\n const attachmentBytes = utf8ByteLength(JSON.stringify(candidate))\n const knownV2 = candidate.kind === ATTENTION_CONTEXT_KIND && candidate.version === ATTENTION_CONTEXT_V2_VERSION\n const knownV3 = candidate.kind === ATTENTION_CONTEXT_KIND && candidate.version === ATTENTION_CONTEXT_V3_VERSION\n const knownV4 = candidate.kind === ATTENTION_CONTEXT_KIND && candidate.version === ATTENTION_CONTEXT_V4_VERSION\n const limit = knownV2 || knownV3 || knownV4\n ? Math.min(maxAttachmentBytes, knownV4 ? ATTENTION_V3_MAX_ENVELOPE_BYTES : knownV3 ? ATTENTION_V3_MAX_ENVELOPE_BYTES : ATTENTION_V2_MAX_ENVELOPE_BYTES)\n : maxAttachmentBytes\n if (attachmentBytes > limit)\n errors.push({ attachment_id: attachmentId, code: 'attachment-bytes-exceeded', path, message: `attachment exceeds ${limit} bytes` })\n const contentBytes = utf8ByteLength(candidate.content as string)\n if (contentBytes !== candidate.content_bytes)\n errors.push({ attachment_id: attachmentId, code: 'content-size-mismatch', path: `${path}.content_bytes`, message: 'content_bytes does not match the canonical payload' })\n if (!/^sha256:[a-f0-9]{64}$/.test(candidate.content_hash as string))\n errors.push({ attachment_id: attachmentId, code: 'invalid-content-hash', path: `${path}.content_hash`, message: 'content_hash must be a lowercase SHA-256 digest' })\n if (!options.allowExpired && candidate.expires_at && Date.parse(candidate.expires_at as string) <= (options.now ?? Date.now()))\n errors.push({ attachment_id: attachmentId, code: 'expired', path: `${path}.expires_at`, message: 'attachment has expired' })\n let payload: unknown\n try {\n payload = JSON.parse(candidate.content as string)\n assertJsonValue(payload)\n }\n catch {\n errors.push({ attachment_id: attachmentId, code: 'invalid-envelope', path: `${path}.content`, message: 'content must be valid bounded JSON' })\n return\n }\n if (canonicalJson(payload as JsonValue) !== candidate.content) {\n errors.push({ attachment_id: attachmentId, code: 'noncanonical-content', path: `${path}.content`, message: 'content must use canonical JSON encoding' })\n return\n }\n const isVisualV1 = candidate.kind === ATTENTION_VISUAL_CONTEXT_KIND && candidate.version === ATTENTION_VISUAL_CONTEXT_VERSION\n if (containsForbiddenContextKey(payload, isVisualV1)) {\n errors.push({ attachment_id: attachmentId, code: 'forbidden-live-field', path: `${path}.content`, message: 'durable context must not contain live capability or bearer fields' })\n return\n }\n if (candidate.kind === ATTENTION_CONTEXT_KIND && candidate.version === ATTENTION_CONTEXT_VERSION) {\n if (!isAttentionSnapshotV1(payload))\n errors.push({ attachment_id: attachmentId, code: 'invalid-attention-payload', path: `${path}.content`, message: 'wippy.attention v1 content is invalid or contains a forbidden live capability field' })\n expandedAttentionBytes += contentBytes\n }\n if (knownV2) {\n try {\n const expanded = decodeAttentionSnapshotV2(payload, ATTENTION_MAX_EXPANDED_BYTES - expandedAttentionBytes)\n expandedAttentionBytes += utf8ByteLength(compactJson(expanded))\n }\n catch {\n errors.push({ attachment_id: attachmentId, code: 'invalid-attention-payload', path: `${path}.content`, message: 'wippy.attention v2 content is invalid or exceeds expansion budgets' })\n }\n }\n if (knownV3) {\n try {\n const expanded = decodeAttentionSnapshotV3(payload, ATTENTION_MAX_EXPANDED_BYTES - expandedAttentionBytes)\n expandedAttentionBytes += utf8ByteLength(compactJson(expanded))\n }\n catch {\n errors.push({ attachment_id: attachmentId, code: 'invalid-attention-payload', path: `${path}.content`, message: 'wippy.attention v3 content is invalid or exceeds expansion budgets' })\n }\n }\n if (knownV4) {\n try {\n const expanded = decodeAttentionSnapshotV4(payload, ATTENTION_MAX_EXPANDED_BYTES - expandedAttentionBytes)\n expandedAttentionBytes += utf8ByteLength(compactJson(expanded))\n }\n catch {\n errors.push({ attachment_id: attachmentId, code: 'invalid-attention-payload', path: `${path}.content`, message: 'wippy.attention v4 content is invalid or exceeds expansion budgets' })\n }\n }\n if (expandedAttentionBytes > ATTENTION_MAX_EXPANDED_BYTES)\n errors.push({ attachment_id: attachmentId, code: 'invalid-attention-payload', path: `${path}.content`, message: 'Attention expansion exceeds the aggregate byte budget' })\n if (isVisualV1) {\n if (!isAttentionVisualContextV1(payload) || candidate.expires_at !== payload.expires_at)\n errors.push({ attachment_id: attachmentId, code: 'invalid-attention-payload', path: `${path}.content`, message: 'wippy.attention.visual v1 content is invalid or contains a forbidden live capability field' })\n }\n })\n if (errors.length > 0)\n return { ok: false, errors }\n return { ok: true, attachments: value as ContextAttachmentEnvelope[] }\n}\n\nexport async function validateContextAttachmentsIntegrity(\n value: unknown,\n options: ContextAttachmentValidationOptions = {},\n): Promise<ContextAttachmentValidationResult> {\n const structural = validateContextAttachments(value, options)\n if (!structural.ok)\n return structural\n\n const errors: ContextAttachmentValidationError[] = []\n for (let index = 0; index < structural.attachments.length; index++) {\n const attachment = structural.attachments[index]\n if (await sha256Text(attachment.content) !== attachment.content_hash) {\n errors.push({\n attachment_id: attachment.attachment_id,\n code: 'content-hash-mismatch',\n path: `context_attachments[${index}].content_hash`,\n message: 'content_hash does not match the canonical payload',\n })\n }\n if (attachment.kind === ATTENTION_CONTEXT_KIND && (attachment.version === ATTENTION_CONTEXT_VERSION\n || attachment.version === ATTENTION_CONTEXT_V2_VERSION\n || attachment.version === ATTENTION_CONTEXT_V3_VERSION\n || attachment.version === ATTENTION_CONTEXT_V4_VERSION)) {\n const payload = JSON.parse(attachment.content)\n const snapshot = attachment.version === ATTENTION_CONTEXT_V2_VERSION\n ? decodeAttentionSnapshotV2(payload)\n : attachment.version === ATTENTION_CONTEXT_V3_VERSION\n ? decodeAttentionSnapshotV3(payload)\n : attachment.version === ATTENTION_CONTEXT_V4_VERSION\n ? decodeAttentionSnapshotV4(payload)\n : payload as AttentionSnapshotV1\n for (let candidateIndex = 0; candidateIndex < snapshot.candidates.length; candidateIndex++) {\n const candidate = snapshot.candidates[candidateIndex]\n if (candidate.action_ref && !await attentionPathDigestMatches(candidate.path, candidate.action_ref.path_digest)) {\n errors.push({\n attachment_id: attachment.attachment_id,\n code: 'action-ref-path-digest-mismatch',\n path: `context_attachments[${index}].content.candidates[${candidateIndex}].action_ref.path_digest`,\n message: 'action_ref.path_digest does not match the canonical candidate path',\n })\n }\n }\n }\n }\n\n if (errors.length > 0)\n return { ok: false, errors }\n return structural\n}\n\nexport function createAttentionSampleGrid(origin: AttentionPoint, radiusCssPx = 20, stepCssPx = 5): AttentionQueryPoint[] {\n if (!isPoint(origin))\n throw new RangeError('origin must contain finite CSS-pixel coordinates')\n if (!Number.isFinite(radiusCssPx) || radiusCssPx < 0 || radiusCssPx > 100)\n throw new RangeError('radiusCssPx must be a finite number between 0 and 100')\n if (!Number.isFinite(stepCssPx) || stepCssPx < 1 || stepCssPx > 100)\n throw new RangeError('stepCssPx must be a finite number between 1 and 100')\n const extent = Math.floor(radiusCssPx / stepCssPx)\n const squarePointCount = (extent * 2 + 1) ** 2\n if (squarePointCount > 4096)\n throw new RangeError('sampling grid exceeds the maximum of 4096 points')\n const points: AttentionQueryPoint[] = []\n for (let yIndex = -extent; yIndex <= extent; yIndex++) {\n const y = yIndex * stepCssPx\n for (let xIndex = -extent; xIndex <= extent; xIndex++) {\n const x = xIndex * stepCssPx\n if (x * x + y * y <= radiusCssPx * radiusCssPx) {\n points.push({\n point_id: `p${points.length}`,\n x: origin.x + x,\n y: origin.y + y,\n })\n }\n }\n }\n return points\n}\n","import type { AttentionFocus, AttentionFocusState, AttentionPathSegment, AttentionRealmKind, AttentionRect, AttentionSelectionState, AttentionTargetSummary } from './attention'\nimport { isAttentionFocus, isAttentionFocusState, isAttentionObservedEvent, isAttentionSelection, isAttentionSelectionState, isAttentionStructuralPath, utf8ByteLength } from './attention'\n\nexport interface AttentionNodeRef {\n host_instance_id: string\n node_id: string\n mount_id: string\n generation: number\n}\n\nexport interface AttentionLayoutPanel {\n id: string\n element?: Element | null\n window?: Window | null\n role?: string\n title?: string\n active: boolean\n collapsed: boolean\n drawer: boolean\n floating: boolean\n modal: boolean\n placeholder: boolean\n}\nexport interface AttentionLayoutRegistration {\n instance_id?: string\n root: Element\n panels: AttentionLayoutPanel[]\n breakpoint?: string\n}\nexport interface AttentionLayoutHandle {\n instance_id: string\n update: (state: Omit<AttentionLayoutRegistration, 'instance_id' | 'root'>) => void\n dispose: () => void\n}\n\nexport interface AttentionInspectionRevisions {\n tree: number\n observation: number\n geometry: number\n}\n\nexport interface AttentionNodeGeometry {\n rect: AttentionRect | null\n clip: AttentionRect | null\n visible: boolean\n offscreen: boolean\n quality: 'exact' | 'approximate' | 'unavailable'\n coordinate_space: 'host-viewport' | { mount_id: string, generation: number }\n}\n\nexport interface AttentionInspectionNode {\n ref: AttentionNodeRef\n parent: AttentionNodeRef | null\n kind: AttentionRealmKind | 'document' | 'semantic' | 'placeholder'\n path: AttentionPathSegment[]\n summary: AttentionTargetSummary\n resource?: { resource_id?: string, package_id?: string, package_version?: string, tag_name?: string, title?: string }\n layout?: { instance_id: string, panel_id: string, role?: string, title?: string, breakpoint?: string, active: boolean, collapsed: boolean, drawer: boolean, floating: boolean, modal: boolean }\n geometry?: AttentionNodeGeometry\n state: 'mounted' | 'hidden' | 'unmounted' | 'unavailable'\n}\n\nexport type AttentionInspectionReason = 'excluded' | 'detached' | 'navigation' | 'disconnected' | 'unauthorized'\n | 'unsupported-version' | 'unsupported-transform' | 'deadline-exceeded'\n | 'node-limit' | 'depth-limit' | 'byte-limit' | 'work-limit' | 'registry-limit'\n | 'revision-changed' | 'provider-unavailable' | 'invalid-request' | 'backpressure'\n\nexport interface AttentionLocalTree {\n root: AttentionNodeRef | null\n nodes: AttentionInspectionNode[]\n revisions: AttentionInspectionRevisions\n omissions: Array<{ node?: AttentionNodeRef, reason: AttentionInspectionReason }>\n complete: boolean\n examined: number\n next_offset?: number\n}\n\nexport type AttentionInspectionOperation = 'cursor' | 'focus' | 'selection' | 'point' | 'tree' | 'find' | 'geometry' | 'subscribe' | 'unsubscribe' | 'change'\nexport type AttentionChangeKind = 'cursor' | 'focus' | 'selection' | 'tree' | 'geometry' | 'invalidation'\nexport interface AttentionNotification {\n subscription_id: string\n sequence: number\n revisions: AttentionInspectionRevisions\n kind: AttentionChangeKind\n nodes: AttentionNodeRef[]\n dropped: number\n reason?: 'backpressure' | 'disconnected' | 'detached'\n closed?: boolean\n}\nexport interface AttentionSubscriptionOptions extends AttentionInspectionScope { events: AttentionChangeKind[] }\nexport interface AttentionSubscription { dispose: () => void }\nexport type AttentionInspectionOutcome = 'ok' | 'empty' | 'cleared' | 'unknown' | 'partial' | 'unavailable' | 'stale' | 'cancelled'\nexport interface AttentionInspectionScope { fromRoot?: boolean, node?: AttentionNodeRef }\nexport type AttentionSearch = { node_id: string } | { role?: string, name?: string, text?: string, resource_id?: string } | { css: string, scope: AttentionNodeRef }\nexport interface AttentionInspectionResult {\n request_id: string\n host_instance_id: string\n measured_at: string\n revisions: AttentionInspectionRevisions\n outcome: AttentionInspectionOutcome\n data: Record<string, unknown> | null\n omissions: Array<{ node?: AttentionNodeRef, reason: AttentionInspectionReason }>\n continuation?: string\n}\nexport interface AttentionInspectionCall {\n operation: AttentionInspectionOperation\n scope: AttentionInspectionScope\n args: Record<string, unknown>\n}\n\nexport function isAttentionNodeRef(value: unknown): value is AttentionNodeRef {\n if (!value || typeof value !== 'object' || Array.isArray(value))\n return false\n const ref = value as Record<string, unknown>\n return Object.keys(ref).length === 4\n && ['host_instance_id', 'node_id', 'mount_id'].every(key => typeof ref[key] === 'string' && utf8ByteLength(ref[key]) > 0 && utf8ByteLength(ref[key]) <= 160)\n && Number.isSafeInteger(ref.generation) && Number(ref.generation) > 0\n}\n\nexport interface AttentionInspectionMount { mount_id: string, generation: number }\nexport interface AttentionInspectionObservation {\n focus: AttentionFocus | null\n focus_state?: AttentionFocusState\n selection_state: AttentionSelectionState\n anchor: AttentionNodeRef | null\n focus_node: AttentionNodeRef | null\n}\nexport function isAttentionInspectionObservation(value: unknown): value is AttentionInspectionObservation {\n return object(value)\n && keys(value, ['focus', 'focus_state', 'selection_state', 'anchor', 'focus_node'])\n && (value.focus === null || isAttentionFocus(value.focus))\n && (value.focus_state === undefined || isAttentionFocusState(value.focus_state))\n && isAttentionSelectionState(value.selection_state)\n && (value.anchor === null || isAttentionNodeRef(value.anchor))\n && (value.focus_node === null || isAttentionNodeRef(value.focus_node))\n}\nexport interface AttentionInspectionRequest extends AttentionInspectionCall {\n schema: 'wippy.attention.relay.v2'\n message_type: 'inspect-request'\n request_id: string\n host_instance_id: string\n source_mount: AttentionInspectionMount\n target_mount: AttentionInspectionMount\n origin_mount: AttentionInspectionMount\n capability_token: string\n sequence: number\n created_at: string\n deadline_at: string\n budget: { nodes: number, depth: number, queries: number, work: number, bytes: number }\n}\nexport interface AttentionInspectionResponse extends AttentionInspectionResult {\n schema: 'wippy.attention.relay.v2'\n message_type: 'inspect-result'\n source_mount: AttentionInspectionMount\n sequence: number\n}\nexport interface AttentionInspectionCancel {\n schema: 'wippy.attention.relay.v2'\n message_type: 'inspect-cancel'\n request_id: string\n host_instance_id: string\n source_mount: AttentionInspectionMount\n target_mount: AttentionInspectionMount\n capability_token: string\n sequence: number\n}\nexport type AttentionInspectionMessage = AttentionInspectionRequest | AttentionInspectionResponse | AttentionInspectionCancel\n\nfunction object(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\nfunction text(value: unknown, max = 256): value is string {\n return typeof value === 'string' && value.length > 0 && value.length <= max\n}\nfunction keys(value: Record<string, unknown>, allowed: string[]): boolean {\n return Object.keys(value).every(key => allowed.includes(key))\n}\nfunction integer(value: unknown, minimum = 0, maximum = Number.MAX_SAFE_INTEGER): boolean {\n return Number.isSafeInteger(value) && Number(value) >= minimum && Number(value) <= maximum\n}\nfunction wireBytes(value: unknown): number {\n try {\n return utf8ByteLength(JSON.stringify(value))\n }\n catch {\n return Number.POSITIVE_INFINITY\n }\n}\nfunction mount(value: unknown): value is AttentionInspectionMount {\n return object(value) && Object.keys(value).length === 2 && text(value.mount_id, 160) && utf8ByteLength(value.mount_id) <= 160 && integer(value.generation, 1)\n}\nfunction revisions(value: unknown): boolean {\n return object(value) && Object.keys(value).length === 3 && ['tree', 'observation', 'geometry'].every(key => integer(value[key]))\n}\n\nexport function isAttentionChangeKind(value: unknown): value is AttentionChangeKind {\n return ['cursor', 'focus', 'selection', 'tree', 'geometry', 'invalidation'].includes(String(value))\n}\n\nexport function isAttentionNotification(value: unknown): value is AttentionNotification {\n return object(value) && keys(value, ['subscription_id', 'sequence', 'revisions', 'kind', 'nodes', 'dropped', 'reason', 'closed'])\n && text(value.subscription_id) && integer(value.sequence, 1) && revisions(value.revisions)\n && isAttentionChangeKind(value.kind) && integer(value.dropped)\n && Array.isArray(value.nodes) && value.nodes.length <= 128 && value.nodes.every(isAttentionNodeRef)\n && (value.reason === undefined || ['backpressure', 'disconnected', 'detached'].includes(String(value.reason)))\n && (value.closed === undefined || typeof value.closed === 'boolean')\n && wireBytes(value) <= 16_384\n}\n\nfunction boundedFields(value: unknown, allowed: string[], maximum = 1024): value is Record<string, string> {\n return object(value) && keys(value, allowed)\n && Object.values(value).every(member => typeof member === 'string' && utf8ByteLength(member) <= maximum)\n}\n\nfunction rect(value: unknown): boolean {\n return object(value) && Object.keys(value).length === 4 && keys(value, ['x', 'y', 'width', 'height'])\n && Object.values(value).every(member => typeof member === 'number' && Number.isFinite(member))\n && Number(value.width) >= 0 && Number(value.height) >= 0\n}\n\nfunction geometry(value: unknown): boolean {\n return object(value) && Object.keys(value).length === 6 && keys(value, ['rect', 'clip', 'visible', 'offscreen', 'quality', 'coordinate_space'])\n && (value.rect === null || rect(value.rect)) && (value.clip === null || rect(value.clip))\n && typeof value.visible === 'boolean' && typeof value.offscreen === 'boolean'\n && ['exact', 'approximate', 'unavailable'].includes(String(value.quality))\n && (value.coordinate_space === 'host-viewport' || mount(value.coordinate_space))\n && (value.quality !== 'unavailable' || (value.rect === null && value.clip === null))\n}\n\nfunction inspectionSummary(value: unknown): boolean {\n if (!object(value) || !keys(value, ['role', 'name', 'text', 'value', 'state']))\n return false\n const { state, ...labels } = value\n return boundedFields(labels, ['role', 'name', 'text', 'value'])\n && (state === undefined || (object(state) && Object.keys(state).length <= 16\n && Object.entries(state).every(([key, member]) => utf8ByteLength(key) <= 64\n && (member === null || typeof member === 'boolean' || (typeof member === 'number' && Number.isFinite(member))\n || (typeof member === 'string' && utf8ByteLength(member) <= 256)))))\n}\n\nexport function isAttentionInspectionNode(value: unknown): value is AttentionInspectionNode {\n if (!object(value) || !keys(value, ['ref', 'parent', 'kind', 'path', 'summary', 'resource', 'layout', 'geometry', 'state'])\n || !isAttentionNodeRef(value.ref) || (value.parent !== null && !isAttentionNodeRef(value.parent))\n || !['host', 'panel', 'artifact', 'page', 'iframe', 'web-fragment', 'web-component', 'shadow-root', 'element', 'document', 'semantic', 'placeholder'].includes(String(value.kind))\n || !['mounted', 'hidden', 'unmounted', 'unavailable'].includes(String(value.state))\n || !isAttentionStructuralPath(value.path) || !object(value.summary)\n || !keys(value.summary, ['role', 'name', 'text', 'value', 'state'])) {\n return false\n }\n if (!inspectionSummary(value.summary) || (value.parent !== null && value.parent.host_instance_id !== value.ref.host_instance_id)) {\n return false\n }\n if (value.resource !== undefined && !boundedFields(value.resource, ['resource_id', 'package_id', 'package_version', 'tag_name', 'title']))\n return false\n if (value.layout !== undefined) {\n const layout = value.layout\n const flags = ['active', 'collapsed', 'drawer', 'floating', 'modal']\n if (!object(layout) || !keys(layout, ['instance_id', 'panel_id', 'role', 'title', 'breakpoint', ...flags])\n || !text(layout.instance_id) || !text(layout.panel_id)\n || flags.some(key => typeof layout[key] !== 'boolean')\n || ['role', 'title', 'breakpoint'].some(key => layout[key] !== undefined && (!text(layout[key]) || utf8ByteLength(String(layout[key])) > 256))) {\n return false\n }\n }\n return value.geometry === undefined || geometry(value.geometry)\n}\n\nfunction inspectionData(value: unknown, host: string): boolean {\n if (value === null)\n return true\n if (!object(value))\n return false\n const ref = (member: unknown) => isAttentionNodeRef(member) && member.host_instance_id === host\n const node = (member: unknown) => isAttentionInspectionNode(member) && member.ref.host_instance_id === host\n if ('notification' in value)\n return keys(value, ['notification']) && isAttentionNotification(value.notification) && value.notification.nodes.every(ref)\n if ('subscription_id' in value)\n return keys(value, ['subscription_id']) && text(value.subscription_id, 160)\n if ('nodes' in value) {\n if (!keys(value, ['nodes', 'root', 'event', 'point', 'observation']) || !Array.isArray(value.nodes)\n || value.nodes.length > 256 || !value.nodes.every(node)) {\n return false\n }\n return (value.root === undefined || value.root === null || ref(value.root))\n && (value.event === undefined || value.event === null || isAttentionObservedEvent(value.event))\n && (value.point === undefined || (object(value.point) && Object.keys(value.point).length === 2 && keys(value.point, ['x', 'y'])\n && typeof value.point.x === 'number' && Number.isFinite(value.point.x) && typeof value.point.y === 'number' && Number.isFinite(value.point.y)))\n && (value.observation === undefined || (isAttentionInspectionObservation(value.observation)\n && (!value.observation.focus || inspectionSummary(value.observation.focus.summary))\n && (value.observation.anchor === null || ref(value.observation.anchor))\n && (value.observation.focus_node === null || ref(value.observation.focus_node))))\n }\n if ('selection' in value) {\n return Object.keys(value).length === 3 && keys(value, ['selection', 'anchor', 'focus'])\n && (value.selection === null || isAttentionSelection(value.selection))\n && (value.anchor === null || ref(value.anchor)) && (value.focus === null || ref(value.focus))\n }\n if ('focus' in value) {\n return Object.keys(value).length === 2 && keys(value, ['focus', 'node'])\n && (value.focus === null || (isAttentionFocus(value.focus) && inspectionSummary(value.focus.summary)))\n && (value.node === null || node(value.node))\n }\n if ('node' in value) {\n const { node: geometryNode, ...metrics } = value\n return ref(geometryNode) && geometry(metrics)\n }\n return false\n}\n\n/** The shared v2 envelope is validated after source/origin authentication. */\nexport function isAttentionInspectionMessage(value: unknown): value is AttentionInspectionMessage {\n if (!object(value) || value.schema !== 'wippy.attention.relay.v2' || !text(value.request_id) || !text(value.host_instance_id)\n || !mount(value.source_mount) || !integer(value.sequence) || wireBytes(value) > 262_144) {\n return false\n }\n const common = ['schema', 'message_type', 'request_id', 'host_instance_id', 'source_mount', 'sequence']\n if (value.message_type === 'inspect-result') {\n if (!keys(value, [...common, 'measured_at', 'revisions', 'outcome', 'data', 'omissions', 'continuation'])\n || !text(value.measured_at, 64) || !Number.isFinite(Date.parse(value.measured_at)) || !revisions(value.revisions)\n || !['ok', 'empty', 'cleared', 'unknown', 'partial', 'unavailable', 'stale', 'cancelled'].includes(String(value.outcome))\n || !Array.isArray(value.omissions) || value.omissions.length > 256\n || value.omissions.some(item => !object(item) || !keys(item, ['node', 'reason']) || !text(item.reason, 64) || (item.node !== undefined && !isAttentionNodeRef(item.node)))\n || (value.continuation !== undefined && !text(value.continuation))) {\n return false\n }\n return inspectionData(value.data, value.host_instance_id)\n }\n if (!mount(value.target_mount) || !text(value.capability_token, 512))\n return false\n if (value.message_type === 'inspect-cancel')\n return keys(value, [...common, 'target_mount', 'capability_token'])\n if (value.message_type !== 'inspect-request' || !keys(value, [...common, 'target_mount', 'origin_mount', 'capability_token', 'created_at', 'deadline_at', 'operation', 'scope', 'args', 'budget'])\n || !mount(value.origin_mount) || !text(value.created_at, 64) || !text(value.deadline_at, 64)\n || !Number.isFinite(Date.parse(value.created_at)) || !Number.isFinite(Date.parse(value.deadline_at))\n || Date.parse(value.deadline_at) - Date.parse(value.created_at) > 1500\n || Date.parse(value.deadline_at) <= Date.parse(value.created_at)\n || !['cursor', 'focus', 'selection', 'point', 'tree', 'find', 'geometry', 'subscribe', 'unsubscribe', 'change'].includes(String(value.operation))\n || !object(value.scope) || !keys(value.scope, ['fromRoot', 'node'])\n || (value.scope.fromRoot !== undefined && typeof value.scope.fromRoot !== 'boolean')\n || (value.scope.node !== undefined && !isAttentionNodeRef(value.scope.node)) || !object(value.args)\n || !object(value.budget) || Object.keys(value.budget).length !== 5) {\n return false\n }\n return integer(value.budget.nodes, 1, 256) && integer(value.budget.depth, 0, 32)\n && integer(value.budget.queries, 1, 128) && integer(value.budget.work, 0, 8192) && integer(value.budget.bytes, 1, 262_144)\n}\n","import type { AttentionPathSegment } from './attention'\nimport { ATTENTION_RELAY_SCHEMA, canonicalJson, isAttentionRelayMessageV1, isAttentionStructuralPath, utf8ByteLength } from './attention'\nimport { isAttentionInspectionMessage } from './attention-inspection'\n\nexport const ATTENTION_RELAY_SCHEMA_V2 = 'wippy.attention.relay.v2'\nconst MAX_WIRE_BYTES = 1_048_576\nconst MAX_EXPANDED_BYTES = 2 * MAX_WIRE_BYTES\nconst MAX_DICTIONARY_SEGMENTS = 8192\nconst PATH_KEYS = new Set(['path', 'anchor_path', 'focus_path'])\nconst receivedBytes = new WeakMap<object, number>()\n\nfunction record(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n\n/** Geometry and every structural segment are retained; only repeated segments are interned. */\nexport function encodeAttentionRelayMessage(value: unknown, version: 1 | 2 = 1): unknown {\n if (version === 1 || !record(value) || value.schema !== ATTENTION_RELAY_SCHEMA)\n return value\n const dictionary: AttentionPathSegment[] = []\n const indices = new Map<string, number>()\n const encode = (item: unknown, key = ''): unknown => {\n if (PATH_KEYS.has(key)) {\n if (!isAttentionStructuralPath(item))\n throw new TypeError('Invalid Attention relay path')\n return item.map((segment) => {\n const canonical = canonicalJson(JSON.parse(JSON.stringify(segment)))\n let index = indices.get(canonical)\n if (index === undefined) {\n index = dictionary.length\n if (index >= MAX_DICTIONARY_SEGMENTS)\n throw new RangeError('Attention relay path dictionary limit exceeded')\n indices.set(canonical, index)\n dictionary.push(segment)\n }\n return index\n })\n }\n if (Array.isArray(item))\n return item.map(entry => encode(entry))\n if (record(item))\n return Object.fromEntries(Object.entries(item).map(([name, entry]) => [name, encode(entry, name)]))\n return item\n }\n return { ...encode(value) as Record<string, unknown>, schema: ATTENTION_RELAY_SCHEMA_V2, path_dictionary: dictionary }\n}\n\n/** Decode before semantic validation, but only after the caller checks source and origin. */\nexport function decodeAttentionRelayMessage(value: unknown): unknown {\n if (!record(value) || value.schema !== ATTENTION_RELAY_SCHEMA_V2)\n return value\n if (isAttentionInspectionMessage(value))\n return value\n const wireBytes = utf8ByteLength(JSON.stringify(value))\n if (wireBytes > MAX_WIRE_BYTES || !Array.isArray(value.path_dictionary)\n || value.path_dictionary.length > MAX_DICTIONARY_SEGMENTS\n || value.path_dictionary.some(segment => !isAttentionStructuralPath([segment]))) {\n return undefined\n }\n const dictionary = value.path_dictionary as AttentionPathSegment[]\n let visits = 0\n const decode = (item: unknown, key = '', depth = 0): unknown => {\n if (++visits > 65_536 || depth > 32)\n throw new RangeError('Attention relay expansion limit exceeded')\n if (PATH_KEYS.has(key)) {\n if (!Array.isArray(item) || item.length === 0 || item.length > 32\n || item.some(index => !Number.isSafeInteger(index) || index < 0 || index >= dictionary.length)) {\n throw new TypeError('Invalid Attention relay path reference')\n }\n return item.map(index => ({ ...dictionary[index] }))\n }\n if (Array.isArray(item))\n return item.map(entry => decode(entry, '', depth + 1))\n if (record(item))\n return Object.fromEntries(Object.entries(item).map(([name, entry]) => [name, decode(entry, name, depth + 1)]))\n return item\n }\n try {\n const { path_dictionary: _dictionary, ...envelope } = value\n const expanded = { ...decode(envelope) as Record<string, unknown>, schema: ATTENTION_RELAY_SCHEMA }\n if (utf8ByteLength(JSON.stringify(expanded)) > MAX_EXPANDED_BYTES || !isAttentionRelayMessageV1(expanded))\n return undefined\n receivedBytes.set(expanded, wireBytes)\n return expanded\n }\n catch {\n return undefined\n }\n}\n\nexport function attentionRelayByteLength(value: unknown, version?: 1 | 2): number {\n if (version === undefined && record(value)) {\n const actual = receivedBytes.get(value)\n if (actual !== undefined)\n return actual\n }\n return utf8ByteLength(JSON.stringify(version === undefined ? value : encodeAttentionRelayMessage(value, version)))\n}\n","/**\n * Surface portability contract, version 1 (EE2-2339).\n *\n * A **surface** is the page-owned rectangular rendering area the host\n * allocates. The same page package must lay out correctly whether the host\n * renders it as a srcdoc iframe or as a Web Fragment, so the contract names\n * one observable structure both engines materialize:\n *\n * - a query container named `wippy-surface` whose content box IS the\n * allocated surface, and which is a flat-tree ancestor of all page content;\n * - four reserved custom properties pinned to that container.\n *\n * The two engines place that container in different documents — the iframe\n * engine injects it into the srcdoc, the fragment engine uses a host-document\n * wrapper above the reframed shadow roots — but authors see one contract.\n *\n * **Why a wrapper and not `html` / `body`** (measured, Chrome 148):\n * `container-type: size` on the document root yields no block axis at all\n * (`min-height` queries never match, `cqh` resolves to `0px`), and `body`\n * redefines the inline axis through its own margin/padding/border. Only a\n * dedicated element carries both axes faithfully.\n */\n\n/**\n * How the host sizes the surface.\n *\n * - `container` — the host imposes both dimensions. Both axes are queryable.\n * - `content` — the host imposes the inline axis and the page's intrinsic\n * content determines the block axis. Only the inline axis is queryable;\n * exposing a measured block size here would create a resize feedback loop.\n *\n * `auto-height` on a page/artifact is the compatibility spelling of `content`.\n */\nexport type PageSizing = 'container' | 'content'\n\n/**\n * Which engine materialized this surface.\n *\n * `host` is not a page engine: it is a component mounted directly into the\n * host document (or the standalone dev proxy), where no surface is allocated\n * and no query box exists. Such a context reports zero width and `content`\n * sizing — deliberately, rather than borrowing the host viewport, which would\n * be the false viewport equivalence the contract exists to avoid.\n */\nexport type SurfaceEngine = 'iframe' | 'fragment' | 'host'\n\n/**\n * Capabilities a page can require via `wippy.surface.requirements`, or test at\n * runtime via `host.surface.supports()`.\n *\n * The inline axis is the contract baseline and deliberately has no token — it\n * is available on every surface, so requiring it would say nothing.\n *\n * - `block-size` — block-axis queries, `cqb`, aspect-ratio and orientation.\n * Container sizing only.\n * - `surface-scroll` — a page-owned scrollport. Container sizing only.\n * - `registered-hit-testing` / `native-document-hit-testing` — deep hit\n * testing through shadow roots.\n * - `owner-visibility` — the owner-visible field on intersection records.\n *\n * The last three are declared so the vocabulary is stable, but are not\n * implemented in this release: `supports()` reports them false and a package\n * requiring one is rejected before startup rather than rendering degraded.\n */\nexport type SurfaceCapability\n = | 'block-size'\n | 'surface-scroll'\n | 'registered-hit-testing'\n | 'native-document-hit-testing'\n | 'owner-visibility'\n\n/** Every capability token, in declaration order. */\nexport const SURFACE_CAPABILITIES: readonly SurfaceCapability[] = [\n 'block-size',\n 'surface-scroll',\n 'registered-hit-testing',\n 'native-document-hit-testing',\n 'owner-visibility',\n]\n\n/**\n * Capabilities this release can actually deliver, and the sizing each needs.\n *\n * A capability absent from this map is part of the vocabulary but unshipped.\n */\nexport const SURFACE_SUPPORTED_CAPABILITIES: Partial<Record<SurfaceCapability, PageSizing>> = {\n 'block-size': 'container',\n 'surface-scroll': 'container',\n}\n\n/** The only contract version this host implements. */\nexport const SURFACE_CONTRACT_VERSION = 1 as const\n\n/** Reserved query-container name. Applications must not override it. */\nexport const SURFACE_CONTAINER_NAME = 'wippy-surface' as const\n\n/**\n * The four reserved custom properties, pinned to the surface container.\n *\n * Applications and their bundled dependencies must not declare, register\n * (`@property` / `CSS.registerProperty()`), or assign these anywhere — a\n * descendant declaration shadows inheritance and silently unpins the value\n * from the surface.\n *\n * Values are literal CSS-pixel lengths, never `cq*` token streams: native\n * container units can retarget to a nearer eligible container, and a missing\n * axis can fall back to the small viewport.\n */\nexport const SURFACE_WIDTH_PROPERTY = '--wippy-surface-width' as const\nexport const SURFACE_WIDTH_UNIT_PROPERTY = '--wippy-surface-width-unit' as const\nexport const SURFACE_HEIGHT_PROPERTY = '--wippy-surface-height' as const\nexport const SURFACE_HEIGHT_UNIT_PROPERTY = '--wippy-surface-height-unit' as const\n\n/**\n * Immutable geometry snapshot.\n *\n * `height` and `heightUnit` are `null` in content sizing — the block axis is\n * genuinely unavailable there, and a number would imply a viewport\n * equivalence that does not exist.\n */\nexport interface SurfaceSnapshot {\n /** Contract version that produced this snapshot. */\n readonly contract: typeof SURFACE_CONTRACT_VERSION\n /** Monotonic, bumped once per coalesced measurement. */\n readonly revision: number\n readonly engine: SurfaceEngine\n readonly sizing: PageSizing\n /** Physical content-box width in CSS pixels. */\n readonly width: number\n /** 1% of `width`, in CSS pixels. */\n readonly widthUnit: number\n /** Physical content-box height in CSS pixels; `null` in content sizing. */\n readonly height: number | null\n /** 1% of `height`, in CSS pixels; `null` in content sizing. */\n readonly heightUnit: number | null\n}\n\n/** Read API exposed to pages as `host.surface`. */\nexport interface SurfaceApi {\n readonly contract: typeof SURFACE_CONTRACT_VERSION\n /** Which engine rendered this page. Prefer `supports()` for behavior decisions. */\n readonly engine: SurfaceEngine\n readonly sizing: PageSizing\n /**\n * Current geometry, re-read from computed style on **every** access — never\n * memoized, so a value read after a layout change is never frame-stale.\n *\n * That also means it forces a style read each time. Cheap for the occasional\n * read; do not poll it in your own animation frame. Use `onChange` to be told\n * when it moves, and read once per notification.\n */\n readonly snapshot: SurfaceSnapshot\n /** Subscribe to geometry changes. Returns an idempotent unsubscribe. */\n onChange: (listener: (snapshot: SurfaceSnapshot) => void) => () => void\n /** Is a capability available for this surface? */\n supports: (capability: SurfaceCapability) => boolean\n}\n\n/**\n * `wippy.surface` in a page package's `package.json`.\n *\n * Absent means the package predates the contract: it still renders, but the\n * host does not advertise portability for it and declares no requirements.\n */\nexport interface SurfaceManifest {\n contract: typeof SURFACE_CONTRACT_VERSION\n /**\n * Capabilities the page cannot render without. The spelling is\n * `requirements` — matching the normative manifest format. An empty or\n * absent list means the page needs only the baseline inline axis.\n */\n requirements?: SurfaceCapability[]\n}\n\n/**\n * Can this surface deliver `capability`?\n *\n * False for tokens that are part of the vocabulary but unshipped, so a page\n * branching on `supports()` degrades instead of assuming a behavior that is\n * not there.\n */\nexport function sizingSupports(sizing: PageSizing, capability: SurfaceCapability): boolean {\n const required = SURFACE_SUPPORTED_CAPABILITIES[capability]\n return required !== undefined && required === sizing\n}\n","/**\n * Host-owned logical activity for a retained web component.\n *\n * This is deliberately independent from CSS visibility, viewport\n * intersection, and document visibility. Hosts set it before connection and\n * update it without replacing the element when a retained surface changes\n * ownership.\n */\nexport const WIPPY_VISIBILITY_ATTRIBUTE = 'data-wippy-visible'\n\n/** Parse the public attribute. Absent or invalid values are unmanaged. */\nexport function readWippyVisibility(element: Element): boolean | null {\n const value = element.getAttribute(WIPPY_VISIBILITY_ATTRIBUTE)\n if (value === 'true')\n return true\n if (value === 'false')\n return false\n return null\n}\n\n/**\n * Reflect logical activity onto an element without a redundant attribute\n * reaction. Call before appending for the initial value and on ownership\n * changes thereafter; neither path remounts the element.\n */\nexport function setWippyVisibility(element: Element, visible: boolean): void {\n const next = String(visible)\n if (element.getAttribute(WIPPY_VISIBILITY_ATTRIBUTE) !== next)\n element.setAttribute(WIPPY_VISIBILITY_ATTRIBUTE, next)\n}\n","/**\n * Cross-boundary contracts shared between the Wippy Web Host and the\n * `@wippy-fe/*` consumer packages.\n *\n * **Pure contracts and dependency-free DOM helpers.** No framework runtime or\n * `@wippy-fe/proxy` dependency. Importing from this module never loads\n * the proxy globals checker — it is safe to import from anywhere in the\n * host source tree without tripping the `host-import-guard` Vite plugin\n * (this package is on the allowlist).\n *\n * **Status: Draft 1 (preview).** Field names and shapes may change\n * between minor releases until a production consumer drives the corner\n * cases out.\n */\n\n// ─── Layout bus envelopes ────────────────────────────────────────────\n\n/**\n * In-tab broadcast envelope. Same shape on both sides of the bus —\n * iframe panels, host-mounted web components, and headless services\n * all see this object.\n */\nexport interface BroadcastEnvelope {\n channel: string\n payload: unknown\n sourcePanelId: string | null\n targetPanelId: string | null\n}\n\n/**\n * Panel-bound layout bus exposed on `el.__wippyHostBus` for web\n * components mounted as `kind: 'component'` panels. Source / target\n * attribution is set by the host — publishers cannot spoof it.\n */\nexport interface LayoutBusBound {\n /** 1:N broadcast — every other panel receives, sender excluded. */\n broadcast: (channel: string, payload: unknown) => void\n /** 1:1 send — only `target` receives. Unknown targets are no-ops. */\n send: (target: string, channel: string, payload: unknown) => void\n /** Subscribe; returns an `off()` for cleanup. */\n subscribe: (handler: (env: BroadcastEnvelope) => void) => () => void\n}\n\n// ─── Layout mutation targets ─────────────────────────────────────────\n\n/**\n * Drop target positions (relative to a slot rect). Reserved for the\n * future panelDrag capability; declared in v1 for forward compat.\n */\nexport type DropPosition = 'left' | 'right' | 'top' | 'bottom' | 'center' | 'none'\n\n/**\n * Slot size grammar (mirrors `@wippy-fe/layout` `SizeValue`). Tight\n * whitelist — `clamp()`, `minmax()`, `min-content`, `calc()`, em/rem/\n * vh/vw are rejected at validateLayout time. See `@wippy-fe/layout`\n * `helpers.ts` \"Size grammar\" for rationale.\n */\nexport type SizeValue\n = | 'auto'\n | `${number}fr`\n | `${number}px`\n | `${number}%`\n\n/** Pixel-only size, used for min/max and drawer dimensions. */\nexport type PixelSize = `${number}px`\n\n/**\n * Target for addPanel / movePanel. `relativeTo` names a panel already in\n * the tree; `position` picks the drop relative to that panel's rect.\n * Omit `relativeTo` to attach to the root tree (position becomes\n * direction-aware).\n */\nexport interface PanelTarget {\n relativeTo?: string\n position: DropPosition\n size?: SizeValue\n}\n\n// ─── Global-name constants ───────────────────────────────────────────\n//\n// These literal strings name the four `window.*` globals that the host\n// writes and consumer code reads. Both sides MUST agree on the spelling\n// — moving a typo here would silently break communication across the\n// host/proxy boundary. No `Window` augmentation is declared in this\n// package because the augmentation references host-side types\n// (`AppConfig`, `ProxyApiInstance`); host-side `src/shared/constants/global.ts`\n// keeps the `declare global` block.\n\nexport const GLOBAL_CONFIG_VAR = '__WIPPY_APP_CONFIG__' as const\nexport const GLOBAL_PROXY_CONFIG_VAR = '__WIPPY_PROXY_CONFIG__' as const\nexport const GLOBAL_API_PROVIDER = '__WIPPY_APP_API__' as const\nexport const GLOBAL_WEB_COMPONENT_CACHE = '__WIPPY_WEB_COMPONENT_CACHE__' as const\n\n// ─── HTML data-role markers ─────────────────────────────────────────\n//\n// `data-role` attributes that the host and dev-proxy use to find anchor\n// elements in the consuming app's `app.html`. The host removes / inserts\n// scripts at the `@wippy/scripts` element; dev-proxy reads the consuming\n// app's package.json from the `@wippy/package` JSON script (injected at\n// build time by `@wippy-fe/vite-plugin`'s `wippyPackagePlugin()`).\n\nexport const WIPPY_SCRIPTS_DATA_ROLE = '@wippy/scripts' as const\nexport const WIPPY_PACKAGE_DATA_ROLE = '@wippy/package' as const\n\n// ─── Attention Context API ──────────────────────────────────────────\n\nexport type { AttentionInspectionCall, AttentionInspectionNode, AttentionInspectionOperation, AttentionInspectionOutcome, AttentionInspectionReason, AttentionInspectionResult, AttentionInspectionRevisions, AttentionInspectionScope, AttentionLocalTree, AttentionNodeGeometry, AttentionNodeRef, AttentionSearch } from './attention-inspection.ts'\nexport { isAttentionNodeRef } from './attention-inspection.ts'\n\n// ─── Surface portability contract (EE2-2339) ───────────────────────\n\nexport type { AttentionInspectionCancel, AttentionInspectionMessage, AttentionInspectionMount, AttentionInspectionRequest, AttentionInspectionResponse } from './attention-inspection.ts'\n\nexport { isAttentionInspectionMessage, isAttentionInspectionNode, isAttentionInspectionObservation } from './attention-inspection.ts'\n\n// ─── Retained web-component visibility ─────────────────────────────\n\nexport type { AttentionInspectionObservation } from './attention-inspection.ts'\n\nexport type { AttentionLayoutHandle, AttentionLayoutPanel, AttentionLayoutRegistration } from './attention-inspection.ts'\nexport type { AttentionChangeKind, AttentionNotification, AttentionSubscription, AttentionSubscriptionOptions } from './attention-inspection.ts'\nexport { isAttentionChangeKind, isAttentionNotification } from './attention-inspection.ts'\nexport { ATTENTION_RELAY_SCHEMA_V2, attentionRelayByteLength, decodeAttentionRelayMessage, encodeAttentionRelayMessage } from './attention-relay-codec.ts'\nexport type {\n AttentionCandidate,\n AttentionCandidateV2,\n AttentionCapabilityFeature,\n AttentionCapabilityGrantV1,\n AttentionCapabilityRequestV1,\n AttentionCapabilityV1,\n AttentionConfig,\n AttentionContextAttachment,\n AttentionCoordinateSpace,\n AttentionFocus,\n AttentionFocusState,\n AttentionGridEncodingV2,\n AttentionLiveGeometry,\n AttentionLiveQueryCandidateV1,\n AttentionMountCoordinateSpace,\n AttentionMountRef,\n AttentionObservationBatchV1,\n AttentionObservedEvent,\n AttentionOmission,\n AttentionOmissionReason,\n AttentionPathAttributesV3,\n AttentionPathSegment,\n AttentionPathSegmentV3,\n AttentionPoint,\n AttentionPointQueryErrorV1,\n AttentionPointQueryResultV1,\n AttentionPointQueryV1,\n AttentionPointResult,\n AttentionPointResultStatus,\n AttentionPreparedFileV1,\n AttentionQueryBudget,\n AttentionQueryErrorCode,\n AttentionQueryPoint,\n AttentionRealmKind,\n AttentionRect,\n AttentionRectTupleV3,\n AttentionRelayMessageV1,\n AttentionRuntimeBootstrapV1,\n AttentionSampleRefV2,\n AttentionSelection,\n AttentionSelectionClearedState,\n AttentionSelectionQueryErrorV1,\n AttentionSelectionQueryRequestV1,\n AttentionSelectionQueryResultV1,\n AttentionSelectionRange,\n AttentionSelectionSelectedState,\n AttentionSelectionState,\n AttentionSelectionUnknownReason,\n AttentionSelectionUnknownState,\n AttentionSelectionUpdateV1,\n AttentionSnapshotV1,\n AttentionSnapshotV2,\n AttentionSnapshotV3,\n AttentionSnapshotV4,\n AttentionTargetRefV1,\n AttentionTargetSummary,\n AttentionTransform,\n AttentionUiActionCaptureV1,\n AttentionUiActionMessageV1,\n AttentionUiActionMode,\n AttentionUiActionRequestV1,\n AttentionUiActionResultV1,\n AttentionUiActionStatus,\n AttentionVisualCaptureOmissionCode,\n AttentionVisualCaptureScope,\n AttentionVisualContextV1,\n ContextAttachmentEnvelope,\n ContextAttachmentInput,\n ContextAttachmentValidationError,\n ContextAttachmentValidationOptions,\n ContextAttachmentValidationResult,\n JsonPrimitive,\n JsonValue,\n} from './attention.ts'\nexport {\n ATTENTION_CAPABILITY_SCHEMA,\n ATTENTION_CONTEXT_KIND,\n ATTENTION_CONTEXT_SCHEMA,\n ATTENTION_CONTEXT_V2_SCHEMA,\n ATTENTION_CONTEXT_V2_VERSION,\n ATTENTION_CONTEXT_V3_SCHEMA,\n ATTENTION_CONTEXT_V3_VERSION,\n ATTENTION_CONTEXT_V4_SCHEMA,\n ATTENTION_CONTEXT_V4_VERSION,\n ATTENTION_CONTEXT_VERSION,\n ATTENTION_MAX_CANDIDATES,\n ATTENTION_MAX_EXPANDED_BYTES,\n ATTENTION_MAX_PATH_SEGMENTS,\n ATTENTION_MAX_POINT_OMISSIONS,\n ATTENTION_MAX_QUERY_POINTS,\n ATTENTION_MAX_SNAPSHOT_OMISSIONS,\n ATTENTION_RELAY_SCHEMA,\n ATTENTION_RUNTIME_SCHEMA,\n ATTENTION_UI_ACTION_MAX_TTL_MS,\n ATTENTION_UI_ACTION_SCHEMA,\n ATTENTION_V2_MAX_DICTIONARY,\n ATTENTION_V2_MAX_ENVELOPE_BYTES,\n ATTENTION_V2_MAX_MEMBERSHIPS,\n ATTENTION_V2_PREFERRED_CONTENT_BYTES,\n ATTENTION_V3_MAX_ENVELOPE_BYTES,\n ATTENTION_VISUAL_CONTEXT_KIND,\n ATTENTION_VISUAL_CONTEXT_SCHEMA,\n ATTENTION_VISUAL_CONTEXT_VERSION,\n ATTENTION_VISUAL_MAX_BYTES,\n ATTENTION_VISUAL_MAX_HEIGHT_PX,\n ATTENTION_VISUAL_MAX_PIXELS,\n ATTENTION_VISUAL_MAX_TIME_MS,\n ATTENTION_VISUAL_MAX_WIDTH_PX,\n attentionSelectionSemanticKey,\n attentionSemanticPath,\n canonicalJson,\n containsForbiddenContextKey,\n CONTEXT_ATTACHMENT_MAX_BYTES,\n CONTEXT_ATTACHMENTS_MAX_BYTES,\n CONTEXT_ATTACHMENTS_MAX_COUNT,\n createAttentionSampleGrid,\n createContextAttachment,\n decodeAttentionSnapshotV2,\n decodeAttentionSnapshotV2Integrity,\n decodeAttentionSnapshotV3,\n decodeAttentionSnapshotV3Integrity,\n decodeAttentionSnapshotV4,\n decodeAttentionSnapshotV4Integrity,\n encodeAttentionSnapshotV2,\n encodeAttentionSnapshotV3,\n encodeAttentionSnapshotV4,\n isAttentionCapabilityV1,\n isAttentionRelayMessageV1,\n isAttentionRuntimeBootstrapV1,\n isAttentionSelectionState,\n isAttentionSnapshotV1,\n isAttentionSnapshotV2,\n isAttentionSnapshotV3,\n isAttentionSnapshotV4,\n isAttentionStructuralPath,\n isAttentionUiActionMessageV1,\n isAttentionVisualContextV1,\n matchesAttentionQueryResult,\n matchesAttentionSelectionQueryResult,\n parseContextAttachmentContent,\n sha256AttentionPath,\n sha256Json,\n sha256Text,\n utf8ByteLength,\n validateContextAttachments,\n validateContextAttachmentsIntegrity,\n} from './attention.ts'\nexport type {\n PageSizing,\n SurfaceApi,\n SurfaceCapability,\n SurfaceEngine,\n SurfaceManifest,\n SurfaceSnapshot,\n} from './surface.ts'\nexport {\n sizingSupports,\n SURFACE_CAPABILITIES,\n SURFACE_CONTAINER_NAME,\n SURFACE_CONTRACT_VERSION,\n SURFACE_HEIGHT_PROPERTY,\n SURFACE_HEIGHT_UNIT_PROPERTY,\n SURFACE_SUPPORTED_CAPABILITIES,\n SURFACE_WIDTH_PROPERTY,\n SURFACE_WIDTH_UNIT_PROPERTY,\n} from './surface.ts'\nexport { readWippyVisibility, setWippyVisibility, WIPPY_VISIBILITY_ATTRIBUTE } from './visibility.ts'\n","/**\n * `wippyPagePlugin` — for `view.page` packages (those with `wippy.type === 'page'`).\n *\n * Two outputs from a single resolved JSON source:\n * 1. An inline `<script type=\"application/json\" data-role=\"@wippy/package\">`\n * injected at the top of `app.html`'s `<head>`. Read at boot by\n * host-less dev-proxy.\n * 2. `wippy-meta.json` emitted into Vite's `outDir`. Read at request time\n * by `wippy/views` in wippy-hosted mode.\n *\n * Both contain the consumer's `package.json` with every `file://<relative>`\n * reference replaced by its file contents. Single source = identical render\n * in either mode.\n */\n\nimport type { Plugin, ResolvedConfig } from 'vite'\nimport type { LoadedPackage } from './shared'\nimport { resolve } from 'node:path'\nimport { WIPPY_PACKAGE_DATA_ROLE } from '@wippy-fe/shared'\nimport { defangScriptClose, loadPackage, resolveFileRefs, validatePagePackage } from './shared'\n\nexport interface WippyPagePluginOptions {\n /** Override the `package.json` path. Defaults to `<viteRoot>/package.json`. */\n packageJsonPath?: string\n /**\n * If `true`, fail the build when `package.json` is missing. If `false`\n * (default), the plugin silently no-ops — useful for shared configs\n * where some target apps don't need the injection.\n */\n required?: boolean\n}\n\nexport function wippyPagePlugin(options: WippyPagePluginOptions = {}): Plugin {\n let root: string\n // Cache so transformIndexHtml + generateBundle don't re-read package.json\n // and don't double-resolve file:// refs within the same build.\n let resolvedSerialized: string | null = null\n\n function compute(): string | null {\n if (resolvedSerialized !== null)\n return resolvedSerialized\n const pkgPath = options.packageJsonPath ?? resolve(root, 'package.json')\n let loaded: LoadedPackage\n try {\n loaded = loadPackage(pkgPath)\n }\n catch (err) {\n if (options.required)\n throw new Error(`[@wippy-fe/vite-plugin] failed to read ${pkgPath}: ${(err as Error).message}`)\n return null\n }\n // Strict spec validation — throws on any wippy-block shape violation\n // (wrong `wippy.type`, missing `wippy.path`, forbidden `wippy.tagName`,\n // etc.). We throw rather than warn so misconfigurations fail the build\n // instead of silently shipping broken metadata. See shared.ts for the\n // full rule set.\n validatePagePackage(loaded.pkg, pkgPath)\n\n // `file://` basename convention violations also throw: the\n // `.do-not-link.` infix is the AI-readable directive that prevents\n // agents from `<link>`-ing the file directly. A consumer using\n // `file://theme.css` defeats that signal even though the resolved\n // content is correct, so we fail the build to force the rename.\n const resolvedPkg = resolveFileRefs(loaded.pkg, loaded.baseDir, (msg) => {\n throw new Error(msg)\n })\n resolvedSerialized = JSON.stringify(resolvedPkg)\n return resolvedSerialized\n }\n\n // Build-time reframed-incompatibility scan (EE2-2313). Warn once per hostile\n // API so a fragment-incompatible page is caught at BUILD, not in prod. Skipped\n // when the page already opts out (wippy.renderEngine: 'iframe').\n const warnedApis = new Set<string>()\n let optedOut: boolean | null = null\n const isOptedOut = (): boolean => {\n if (optedOut === null) {\n try {\n optedOut = (JSON.parse(compute() ?? '{}')?.wippy?.renderEngine) === 'iframe'\n }\n catch {\n optedOut = false\n }\n }\n return optedOut\n }\n\n return {\n name: '@wippy-fe/vite-plugin:wippy-page',\n enforce: 'pre',\n configResolved(config: ResolvedConfig) {\n root = config.root\n resolvedSerialized = null\n },\n transform(code: string, id: string) {\n if (/node_modules/.test(id) || !/\\.(?:vue|m?[jt]sx?)$/.test(id) || isOptedOut())\n return null\n const hostile: Array<[RegExp, string]> = [\n [/\\belementFromPoint\\b/, 'elementFromPoint (hit-testing returns null in a reframed realm)'],\n [/\\bwindow\\.scroll(?:X|Y|To|By)?\\b/, 'window scroll geometry (always 0 in a reframed realm)'],\n [/\\bmatchMedia\\s*\\(/, 'matchMedia (resolves against the HOST viewport, not the fragment panel)'],\n [/\\b\\d+(?:\\.\\d+)?v[hw]\\b/, 'vh/vw units (resolve against the HOST viewport)'],\n [/position\\s*:\\s*fixed/, 'position:fixed (pins to the HOST viewport, not the fragment panel)'],\n ]\n for (const [re, msg] of hostile) {\n if (re.test(code) && !warnedApis.has(msg)) {\n warnedApis.add(msg)\n this.warn(`[@wippy-fe/vite-plugin] this view.page uses ${msg}. Such APIs misbehave inside a Web Fragment. If it will run under the fragment engine, set \\`wippy.renderEngine: \"iframe\"\\` in package.json to render it as a legacy iframe. [EE2-2313]`)\n }\n }\n return null\n },\n transformIndexHtml: {\n order: 'pre',\n handler(html) {\n const json = compute()\n if (json === null)\n return html\n const safeJson = defangScriptClose(json)\n const tag = `<script type=\"application/json\" data-role=\"${WIPPY_PACKAGE_DATA_ROLE}\">${safeJson}</script>`\n if (html.includes('<head>'))\n return html.replace('<head>', `<head>\\n ${tag}`)\n return tag + html\n },\n },\n generateBundle() {\n const json = compute()\n if (json === null)\n return\n this.emitFile({\n type: 'asset',\n fileName: 'wippy-meta.json',\n source: json,\n })\n },\n }\n}\n","/**\n * Shared helpers for `wippyPagePlugin` and `wippyComponentPlugin`.\n *\n * Both plugins produce `dist/wippy-meta.json` — a resolved copy of the\n * consumer's `package.json` with every `file://<relative-path>` reference\n * replaced by the referenced file's UTF-8 contents. The page plugin\n * additionally injects the same resolved JSON into `app.html` as an\n * `<script type=\"application/json\" data-role=\"@wippy/package\">` so host-less\n * mode (gen-2-chat dev-proxy) can read it synchronously at boot.\n *\n * `wippy-meta.json` is the MANDATORY contract between consumers and\n * `wippy/views`. See `web_components.spec.md` § \"Package metadata: source of\n * truth\" for the full spec.\n */\n\nimport { readFileSync } from 'node:fs'\nimport { basename, dirname, resolve } from 'node:path'\n\nexport interface LoadedPackage {\n /** The parsed `package.json` as a JS object. */\n pkg: Record<string, unknown>\n /**\n * Absolute path to the directory containing `package.json` — used as the\n * base for `file://` reference resolution.\n */\n baseDir: string\n}\n\n/**\n * Read + parse `package.json` from disk. Throws if the file is missing or\n * not valid JSON — the build is expected to fail loud rather than silently\n * skip the package.\n */\nexport function loadPackage(packageJsonPath: string): LoadedPackage {\n const raw = readFileSync(packageJsonPath, 'utf-8')\n const pkg = JSON.parse(raw) as Record<string, unknown>\n return { pkg, baseDir: dirname(packageJsonPath) }\n}\n\n/**\n * camelCase → kebab-case conversion used by the `*.do-not-link.<ext>`\n * naming convention. Treats consecutive capitals as a single word so\n * `customCSS` → `custom-css` (not `custom-c-s-s`). Matches lodash's\n * kebabCase semantics for these specific input shapes.\n *\n * Examples codified in tests:\n * - `customCSS` → `custom-css`\n * - `customCss` → `custom-css`\n * - `cssVariables` → `css-variables`\n * - `tailwindConfig` → `tailwind-config`\n * - `description` → `description`\n * - `URLPath` → `url-path`\n */\nexport function kebabize(camel: string): string {\n return camel\n // Insert a hyphen between a run of capitals and a following Capital+lowercase:\n // \"URLPath\" → \"URL-Path\"\n // \"customCSS\" stays \"customCSS\" here (no following lowercase)\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')\n // Insert a hyphen between a lowercase/digit and a following capital:\n // \"customCSS\" → \"custom-CSS\"\n // \"URL-Path\" → \"URL-Path\" (unchanged — already split)\n .replace(/([a-z\\d])([A-Z])/g, '$1-$2')\n .toLowerCase()\n}\n\n/**\n * Validator used by `resolveFileRefs` to surface mismatches between a\n * `file://` reference's basename and the canonical `<field-kebab>.do-not-link.<ext>`\n * convention. Returns the expected basename (for use in the warning\n * message) or `null` if the actual basename already conforms.\n *\n * Convention: basename without extension MUST end with `.do-not-link` and\n * its prefix MUST equal `kebabize(fieldName)`. Examples:\n * - field `customCSS`, file `custom-css.do-not-link.css` → null (ok)\n * - field `customCSS`, file `my-theme.css` → \"custom-css.do-not-link.css\"\n * - field `customCSS`, file `custom-css.css` → \"custom-css.do-not-link.css\"\n *\n * `null` field name (top-level / array index) skips the check.\n */\nexport function expectedDoNotLinkBasename(fieldName: string | null, actualBasename: string): string | null {\n if (!fieldName)\n return null\n const dot = actualBasename.lastIndexOf('.')\n if (dot <= 0)\n return `${kebabize(fieldName)}.do-not-link.<ext>`\n const ext = actualBasename.slice(dot + 1)\n const expected = `${kebabize(fieldName)}.do-not-link.${ext}`\n if (actualBasename === expected)\n return null\n return expected\n}\n\n/**\n * Recursively replace every string value of shape `file://<relative-path>`\n * with the referenced file's UTF-8 contents. Paths are resolved against\n * `baseDir` (typically the directory containing `package.json`).\n *\n * Non-string, non-array, non-object values pass through unchanged. Missing\n * files throw — the convention is that `file://` refs are load-bearing.\n *\n * `onWarn` (optional) is called when a `file://` basename doesn't match the\n * canonical `<field-kebab>.do-not-link.<ext>` convention. The warning is\n * advisory — resolution still succeeds — so consumers can opt out by not\n * passing the callback.\n */\nexport function resolveFileRefs<T>(\n node: T,\n baseDir: string,\n onWarn?: (msg: string) => void,\n fieldName: string | null = null,\n): T {\n if (typeof node === 'string' && node.startsWith('file://')) {\n const rel = node.slice('file://'.length)\n const abs = resolve(baseDir, rel)\n if (onWarn) {\n const expected = expectedDoNotLinkBasename(fieldName, basename(rel))\n if (expected) {\n onWarn(`[@wippy-fe/vite-plugin] file:// basename \"${basename(rel)}\" for field \"${fieldName}\" does not match the *.do-not-link.<ext> convention. Expected: \"${expected}\". See web_components.spec.md § \"Package metadata: source of truth\".`)\n }\n }\n return readFileSync(abs, 'utf-8') as unknown as T\n }\n if (Array.isArray(node))\n return node.map(v => resolveFileRefs(v, baseDir, onWarn, fieldName)) as unknown as T\n if (node && typeof node === 'object') {\n return Object.fromEntries(\n Object.entries(node as Record<string, unknown>)\n .map(([k, v]) => [k, resolveFileRefs(v, baseDir, onWarn, k)]),\n ) as unknown as T\n }\n return node\n}\n\n/**\n * Defang `</script>` sequences inside a JSON string so it's safe to embed\n * verbatim inside an inline `<script type=\"application/json\">` tag. Case\n * insensitive (`</SCRIPT`, `</Script`, etc. all need escaping).\n */\nexport function defangScriptClose(json: string): string {\n return json.replace(/<\\/script/gi, '<\\\\/script')\n}\n\n/**\n * Validate a consumer's `package.json` against the `wippy-component-1.0`\n * spec FOR A PAGE PACKAGE. Throws with a clear, actionable message on any\n * violation. Called by `wippyPagePlugin` at build time so misconfigurations\n * fail the build instead of silently shipping broken metadata.\n *\n * We throw rather than warn because nobody reads build warnings — they\n * scroll past in CI output and get filtered out of IDE diagnostics. A\n * field-shape mistake that warns today is a real bug tomorrow.\n *\n * Rules enforced (must all hold for a page):\n * - Top-level `name` and `version` must be strings.\n * - `wippy` must be a plain object.\n * - `wippy.type`, when set, MUST equal `'page'` (not `'component'`,\n * `'widget'`, or anything else).\n * - `wippy.path` is REQUIRED for pages (HTML entry path).\n * - `wippy.tagName` is FORBIDDEN for pages (custom-element-tag is a\n * component-only concept).\n * - `specification`, when set, MUST equal `'wippy-component-1.0'`.\n */\nexport function validatePagePackage(pkg: Record<string, unknown>, packageJsonPath: string): void {\n const prefix = `[@wippy-fe/vite-plugin] ${packageJsonPath}:`\n\n if (typeof pkg.name !== 'string' || pkg.name.length === 0)\n throw new Error(`${prefix} \\`name\\` is required and must be a non-empty string`)\n if (typeof pkg.version !== 'string' || pkg.version.length === 0)\n throw new Error(`${prefix} \\`version\\` is required and must be a non-empty string`)\n if (pkg.specification !== undefined && pkg.specification !== 'wippy-component-1.0')\n throw new Error(`${prefix} \\`specification\\` must be \"wippy-component-1.0\" (got ${JSON.stringify(pkg.specification)})`)\n\n const wippy = pkg.wippy\n if (wippy === undefined || wippy === null)\n throw new Error(`${prefix} top-level \\`wippy\\` block is required for a page package using wippyPagePlugin()`)\n if (typeof wippy !== 'object' || Array.isArray(wippy))\n throw new Error(`${prefix} \\`wippy\\` must be a JSON object (got ${Array.isArray(wippy) ? 'array' : typeof wippy})`)\n\n const w = wippy as Record<string, unknown>\n\n if (w.type !== undefined && w.type !== 'page')\n throw new Error(`${prefix} \\`wippy.type\\` is \"${String(w.type)}\" but wippyPagePlugin() was used. Either set \\`wippy.type: \"page\"\\` or switch to wippyComponentPlugin() for components/widgets.`)\n\n if (w.path === undefined)\n throw new Error(`${prefix} \\`wippy.path\\` is required for a page package (the relative path to the HTML entry, e.g. \"dist/app.html\")`)\n if (typeof w.path !== 'string' || w.path.length === 0)\n throw new Error(`${prefix} \\`wippy.path\\` must be a non-empty string (got ${JSON.stringify(w.path)})`)\n\n if (w.tagName !== undefined)\n throw new Error(`${prefix} \\`wippy.tagName\\` is set on a page package but the field is component-only. Either remove it or switch to wippyComponentPlugin().`)\n\n // EE2-2313: per-page render engine. The string form must be a known value; the\n // reserved object form ({ route?, artifact? }) is permitted but not read yet.\n if (w.renderEngine !== undefined && typeof w.renderEngine === 'string'\n && w.renderEngine !== 'auto' && w.renderEngine !== 'iframe' && w.renderEngine !== 'fragment') {\n throw new Error(`${prefix} \\`wippy.renderEngine\\` must be \"auto\", \"iframe\", or \"fragment\" (got ${JSON.stringify(w.renderEngine)}). Absent = \"auto\" (follow the deployment's global engine); use \"iframe\" to opt a fragment-incompatible page out.`)\n }\n}\n\n/**\n * Validate a consumer's `package.json` for a component/widget package using\n * `wippyComponentPlugin`. Throws on any violation. Same rationale as\n * `validatePagePackage` — fail loud at build time.\n *\n * Rules enforced (must all hold for a component):\n * - Top-level `name` and `version` must be strings.\n * - `wippy` must be a plain object.\n * - `wippy.type`, when set, MUST be one of `'component'` or `'widget'` (the\n * spec uses both interchangeably for ESM-shipped custom elements).\n * - `wippy.tagName` is REQUIRED for components — without it the host has\n * no element name to register against.\n * - `wippy.path` is FORBIDDEN for components (HTML-entry is a page-only\n * concept; components use top-level `browser` for the ESM entry).\n * - `specification`, when set, MUST equal `'wippy-component-1.0'`.\n * - `browser` SHOULD be set (the ESM entry path); only warn-by-throw if\n * absent because the plugin still emits useful meta without it.\n */\nexport function validateComponentPackage(pkg: Record<string, unknown>, packageJsonPath: string): void {\n const prefix = `[@wippy-fe/vite-plugin] ${packageJsonPath}:`\n\n if (typeof pkg.name !== 'string' || pkg.name.length === 0)\n throw new Error(`${prefix} \\`name\\` is required and must be a non-empty string`)\n if (typeof pkg.version !== 'string' || pkg.version.length === 0)\n throw new Error(`${prefix} \\`version\\` is required and must be a non-empty string`)\n if (pkg.specification !== undefined && pkg.specification !== 'wippy-component-1.0')\n throw new Error(`${prefix} \\`specification\\` must be \"wippy-component-1.0\" (got ${JSON.stringify(pkg.specification)})`)\n\n const wippy = pkg.wippy\n if (wippy === undefined || wippy === null)\n throw new Error(`${prefix} top-level \\`wippy\\` block is required for a component package using wippyComponentPlugin()`)\n if (typeof wippy !== 'object' || Array.isArray(wippy))\n throw new Error(`${prefix} \\`wippy\\` must be a JSON object (got ${Array.isArray(wippy) ? 'array' : typeof wippy})`)\n\n const w = wippy as Record<string, unknown>\n\n if (w.type !== undefined && w.type !== 'component' && w.type !== 'widget')\n throw new Error(`${prefix} \\`wippy.type\\` is \"${String(w.type)}\" but wippyComponentPlugin() was used. Either set \\`wippy.type: \"component\"\\` (or \"widget\") or switch to wippyPagePlugin() for pages.`)\n\n if (w.tagName === undefined)\n throw new Error(`${prefix} \\`wippy.tagName\\` is required for a component package (the custom-element tag name, e.g. \"example-mermaid\"). Without it the host cannot register the element.`)\n if (typeof w.tagName !== 'string' || w.tagName.length === 0)\n throw new Error(`${prefix} \\`wippy.tagName\\` must be a non-empty string (got ${JSON.stringify(w.tagName)})`)\n // Custom Elements spec (PotentialCustomElementName):\n // - MUST start with an ASCII lowercase letter [a-z]\n // - MUST contain a hyphen\n // - MAY contain ASCII lowercase letters, digits, or hyphens\n // (The full spec also allows ., _, and some Unicode ranges, but in practice\n // Wippy tag names only ever use [a-z0-9-]. We keep the check tight rather\n // than full-spec because a \"FOO-BAR\" or \"-leading\" passing validation today\n // would silently break tomorrow when `customElements.define()` rejects it.)\n //\n // Mirror twin: `@wippy-fe/proxy`'s `vue-warn-suppressor.ts` uses an\n // equivalent regex (`/^[a-z][a-z0-9]*-[a-z0-9-]*$/`) to decide whether\n // an unresolved Vue tag \"looks like a pending-registration custom\n // element\". The two patterns match exactly the same name shape — keep\n // them in sync. Functionally identical; differ only in character-class\n // notation (`[\\da-z]` vs `[a-z0-9]`).\n if (!/^[a-z][\\da-z]*-[-\\da-z]*$/.test(w.tagName))\n throw new Error(`${prefix} \\`wippy.tagName\\` \"${w.tagName}\" is not a valid Custom Elements name. Must start with an ASCII lowercase letter, contain a hyphen, and use only lowercase letters, digits, and hyphens. Examples: \"example-mermaid\", \"my-widget-v2\".`)\n // Reserved names that browsers refuse to register (HTML5 spec):\n const reserved = new Set([\n 'annotation-xml',\n 'color-profile',\n 'font-face',\n 'font-face-src',\n 'font-face-uri',\n 'font-face-format',\n 'font-face-name',\n 'missing-glyph',\n ])\n if (reserved.has(w.tagName))\n throw new Error(`${prefix} \\`wippy.tagName\\` \"${w.tagName}\" is a reserved HTML name; browsers will refuse to register it.`)\n\n if (w.path !== undefined)\n throw new Error(`${prefix} \\`wippy.path\\` is set on a component package but the field is page-only. Components use top-level \\`browser\\` for the ESM entry path. Either remove \\`wippy.path\\` or switch to wippyPagePlugin().`)\n\n // EE2-2313: render engine applies to view.page only — a view.component's render\n // path is fixed, so the flag is meaningless (and would silently do nothing) here.\n if (w.renderEngine !== undefined)\n throw new Error(`${prefix} \\`wippy.renderEngine\\` is set on a component package but the field is page-only (a view.component's render path is fixed). Remove it.`)\n}\n","/**\n * `wippyComponentPlugin` — for `view.component` packages (those with\n * `wippy.type === 'component'`).\n *\n * Components ship a JS module, not an HTML entry — there's nothing to inject\n * an inline `<script>` into. The plugin's only job is to emit `wippy-meta.json`\n * into Vite's `outDir` for `wippy/views` to read at request time.\n *\n * The `wippy-meta.json` file is the MANDATORY contract between components and\n * `wippy/views`. See `web_components.spec.md` § \"Package metadata: source of\n * truth\". Without it, `views` falls back to its legacy YAML-synthesis path,\n * which is deprecated and exists only for migration.\n */\n\nimport type { Plugin, ResolvedConfig } from 'vite'\nimport type { LoadedPackage } from './shared'\nimport { resolve } from 'node:path'\nimport { loadPackage, resolveFileRefs, validateComponentPackage } from './shared'\n\nexport interface WippyComponentPluginOptions {\n /** Override the `package.json` path. Defaults to `<viteRoot>/package.json`. */\n packageJsonPath?: string\n /**\n * If `true`, fail the build when `package.json` is missing. If `false`\n * (default), the plugin silently no-ops.\n */\n required?: boolean\n}\n\nexport function wippyComponentPlugin(options: WippyComponentPluginOptions = {}): Plugin {\n let root: string\n\n return {\n name: '@wippy-fe/vite-plugin:wippy-component',\n enforce: 'pre',\n configResolved(config: ResolvedConfig) {\n root = config.root\n },\n generateBundle() {\n const pkgPath = options.packageJsonPath ?? resolve(root, 'package.json')\n let loaded: LoadedPackage\n try {\n loaded = loadPackage(pkgPath)\n }\n catch (err) {\n if (options.required)\n throw new Error(`[@wippy-fe/vite-plugin] failed to read ${pkgPath}: ${(err as Error).message}`)\n return\n }\n // Strict spec validation — throws on any wippy-block shape violation\n // (wrong `wippy.type`, missing `wippy.tagName`, forbidden `wippy.path`,\n // tagName without hyphen, etc.). We throw rather than warn so\n // misconfigurations fail the build instead of silently shipping broken\n // metadata. See shared.ts for the full rule set.\n validateComponentPackage(loaded.pkg, pkgPath)\n\n // `file://` basename convention violations throw — see page-plugin's\n // identical comment for the rationale.\n const resolvedPkg = resolveFileRefs(loaded.pkg, loaded.baseDir, (msg) => {\n throw new Error(msg)\n })\n this.emitFile({\n type: 'asset',\n fileName: 'wippy-meta.json',\n source: JSON.stringify(resolvedPkg),\n })\n },\n }\n}\n","/**\n * Vite plugins for Wippy view.page and view.component packages.\n *\n * Both plugins emit `dist/wippy-meta.json` — the consumer's `package.json`\n * with every `file://<relative-path>` reference replaced by the referenced\n * file's UTF-8 contents. `wippy-meta.json` is the MANDATORY contract between\n * consumers and `wippy/views` ≥ 1.0.31. See `web_components.spec.md` §\n * \"Package metadata: source of truth\" for the full spec.\n *\n * - `wippyPagePlugin()` — for `view.page` packages. Emits `wippy-meta.json`\n * AND injects an inline `<script data-role=\"@wippy/package\">` into the\n * built `app.html` (for host-less mode).\n * - `wippyComponentPlugin()` — for `view.component` packages. Emits\n * `wippy-meta.json` only; components have no HTML entry.\n *\n * Convention: bulky string fields (`customCSS`, `description`, etc.) should\n * be moved into separate files referenced via `file://` and named\n * `<field-name-kebab>.do-not-link.<ext>` — basename matches the kebab-case\n * wippy block field name (e.g. `customCSS` → `custom-css.do-not-link.css`,\n * `description` → `description.do-not-link.md`). The `.do-not-link.` infix\n * is what stops AI agents from `<link>`-ing the file directly into app.html.\n * The proxy injects the content at runtime from the resolved package metadata.\n *\n * **Status: Draft 1 (preview).** API may change between minor releases.\n */\n\nimport type { Plugin } from 'vite'\nimport { WIPPY_PACKAGE_DATA_ROLE } from '@wippy-fe/shared'\nimport { wippyPagePlugin } from './page-plugin'\n\nexport { WIPPY_PACKAGE_DATA_ROLE }\nexport { wippyComponentPlugin } from './component-plugin'\nexport type { WippyComponentPluginOptions } from './component-plugin'\nexport { wippyPagePlugin } from './page-plugin'\nexport type { WippyPagePluginOptions } from './page-plugin'\nexport type { WippyPagePluginOptions as WippyPackagePluginOptions } from './page-plugin'\n\n/**\n * @deprecated Use `wippyPagePlugin()` instead. The new name disambiguates\n * the page-side plugin from `wippyComponentPlugin()` introduced alongside it.\n * This alias preserves backwards compatibility for existing `vite.config.ts`\n * files and will be removed in a future major.\n */\nexport function wippyPackagePlugin(...args: Parameters<typeof wippyPagePlugin>): Plugin {\n return wippyPagePlugin(...args)\n}\n// Re-export the strict validators so CI tooling / external linters can\n// reuse the same rules the plugins enforce at build time.\nexport { validateComponentPackage, validatePagePackage } from './shared'\n"],"mappings":";AACO,IAAM,gCAAgC,KAAK;AAC3C,IAAM,+BAA+B,KAAK;AAU1C,IAAM,uCAAuC,KAAK;AAClD,IAAM,kCAAkC,KAAK;AAC7C,IAAM,kCAAkC,KAAK;AAC7C,IAAM,+BAA+B,MAAM;AAiB3C,IAAM,6BAA6B,OAAO;AE3BjD,IAAM,iBAAiB;AACvB,IAAM,qBAAqB,IAAI;AGgGxB,IAAM,0BAA0B;;;ACrFvC,SAAS,WAAAA,gBAAe;;;ACFxB,SAAS,oBAAoB;AAC7B,SAAS,UAAU,SAAS,eAAe;AAiBpC,SAAS,YAAY,iBAAwC;AAClE,QAAM,MAAM,aAAa,iBAAiB,OAAO;AACjD,QAAM,MAAM,KAAK,MAAM,GAAG;AAC1B,SAAO,EAAE,KAAK,SAAS,QAAQ,eAAe,EAAE;AAClD;AAgBO,SAAS,SAAS,OAAuB;AAC9C,SAAO,MAIJ,QAAQ,yBAAyB,OAAO,EAIxC,QAAQ,qBAAqB,OAAO,EACpC,YAAY;AACjB;AAgBO,SAAS,0BAA0B,WAA0B,gBAAuC;AACzG,MAAI,CAAC;AACH,WAAO;AACT,QAAM,MAAM,eAAe,YAAY,GAAG;AAC1C,MAAI,OAAO;AACT,WAAO,GAAG,SAAS,SAAS,CAAC;AAC/B,QAAM,MAAM,eAAe,MAAM,MAAM,CAAC;AACxC,QAAM,WAAW,GAAG,SAAS,SAAS,CAAC,gBAAgB,GAAG;AAC1D,MAAI,mBAAmB;AACrB,WAAO;AACT,SAAO;AACT;AAeO,SAAS,gBACd,MACA,SACA,QACA,YAA2B,MACxB;AACH,MAAI,OAAO,SAAS,YAAY,KAAK,WAAW,SAAS,GAAG;AAC1D,UAAM,MAAM,KAAK,MAAM,UAAU,MAAM;AACvC,UAAM,MAAM,QAAQ,SAAS,GAAG;AAChC,QAAI,QAAQ;AACV,YAAM,WAAW,0BAA0B,WAAW,SAAS,GAAG,CAAC;AACnE,UAAI,UAAU;AACZ,eAAO,6CAA6C,SAAS,GAAG,CAAC,gBAAgB,SAAS,mEAAmE,QAAQ,yEAAsE;AAAA,MAC7O;AAAA,IACF;AACA,WAAO,aAAa,KAAK,OAAO;AAAA,EAClC;AACA,MAAI,MAAM,QAAQ,IAAI;AACpB,WAAO,KAAK,IAAI,OAAK,gBAAgB,GAAG,SAAS,QAAQ,SAAS,CAAC;AACrE,MAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,WAAO,OAAO;AAAA,MACZ,OAAO,QAAQ,IAA+B,EAC3C,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,gBAAgB,GAAG,SAAS,QAAQ,CAAC,CAAC,CAAC;AAAA,IAChE;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,kBAAkB,MAAsB;AACtD,SAAO,KAAK,QAAQ,eAAe,YAAY;AACjD;AAsBO,SAAS,oBAAoB,KAA8B,iBAA+B;AAC/F,QAAM,SAAS,2BAA2B,eAAe;AAEzD,MAAI,OAAO,IAAI,SAAS,YAAY,IAAI,KAAK,WAAW;AACtD,UAAM,IAAI,MAAM,GAAG,MAAM,sDAAsD;AACjF,MAAI,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,WAAW;AAC5D,UAAM,IAAI,MAAM,GAAG,MAAM,yDAAyD;AACpF,MAAI,IAAI,kBAAkB,UAAa,IAAI,kBAAkB;AAC3D,UAAM,IAAI,MAAM,GAAG,MAAM,yDAAyD,KAAK,UAAU,IAAI,aAAa,CAAC,GAAG;AAExH,QAAM,QAAQ,IAAI;AAClB,MAAI,UAAU,UAAa,UAAU;AACnC,UAAM,IAAI,MAAM,GAAG,MAAM,mFAAmF;AAC9G,MAAI,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK;AAClD,UAAM,IAAI,MAAM,GAAG,MAAM,yCAAyC,MAAM,QAAQ,KAAK,IAAI,UAAU,OAAO,KAAK,GAAG;AAEpH,QAAM,IAAI;AAEV,MAAI,EAAE,SAAS,UAAa,EAAE,SAAS;AACrC,UAAM,IAAI,MAAM,GAAG,MAAM,uBAAuB,OAAO,EAAE,IAAI,CAAC,iIAAiI;AAEjM,MAAI,EAAE,SAAS;AACb,UAAM,IAAI,MAAM,GAAG,MAAM,4GAA4G;AACvI,MAAI,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,WAAW;AAClD,UAAM,IAAI,MAAM,GAAG,MAAM,mDAAmD,KAAK,UAAU,EAAE,IAAI,CAAC,GAAG;AAEvG,MAAI,EAAE,YAAY;AAChB,UAAM,IAAI,MAAM,GAAG,MAAM,oIAAoI;AAI/J,MAAI,EAAE,iBAAiB,UAAa,OAAO,EAAE,iBAAiB,YACzD,EAAE,iBAAiB,UAAU,EAAE,iBAAiB,YAAY,EAAE,iBAAiB,YAAY;AAC9F,UAAM,IAAI,MAAM,GAAG,MAAM,wEAAwE,KAAK,UAAU,EAAE,YAAY,CAAC,mHAAmH;AAAA,EACpP;AACF;AAoBO,SAAS,yBAAyB,KAA8B,iBAA+B;AACpG,QAAM,SAAS,2BAA2B,eAAe;AAEzD,MAAI,OAAO,IAAI,SAAS,YAAY,IAAI,KAAK,WAAW;AACtD,UAAM,IAAI,MAAM,GAAG,MAAM,sDAAsD;AACjF,MAAI,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,WAAW;AAC5D,UAAM,IAAI,MAAM,GAAG,MAAM,yDAAyD;AACpF,MAAI,IAAI,kBAAkB,UAAa,IAAI,kBAAkB;AAC3D,UAAM,IAAI,MAAM,GAAG,MAAM,yDAAyD,KAAK,UAAU,IAAI,aAAa,CAAC,GAAG;AAExH,QAAM,QAAQ,IAAI;AAClB,MAAI,UAAU,UAAa,UAAU;AACnC,UAAM,IAAI,MAAM,GAAG,MAAM,6FAA6F;AACxH,MAAI,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK;AAClD,UAAM,IAAI,MAAM,GAAG,MAAM,yCAAyC,MAAM,QAAQ,KAAK,IAAI,UAAU,OAAO,KAAK,GAAG;AAEpH,QAAM,IAAI;AAEV,MAAI,EAAE,SAAS,UAAa,EAAE,SAAS,eAAe,EAAE,SAAS;AAC/D,UAAM,IAAI,MAAM,GAAG,MAAM,uBAAuB,OAAO,EAAE,IAAI,CAAC,uIAAuI;AAEvM,MAAI,EAAE,YAAY;AAChB,UAAM,IAAI,MAAM,GAAG,MAAM,gKAAgK;AAC3L,MAAI,OAAO,EAAE,YAAY,YAAY,EAAE,QAAQ,WAAW;AACxD,UAAM,IAAI,MAAM,GAAG,MAAM,sDAAsD,KAAK,UAAU,EAAE,OAAO,CAAC,GAAG;AAgB7G,MAAI,CAAC,4BAA4B,KAAK,EAAE,OAAO;AAC7C,UAAM,IAAI,MAAM,GAAG,MAAM,uBAAuB,EAAE,OAAO,uMAAuM;AAElQ,QAAM,WAAW,oBAAI,IAAI;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,MAAI,SAAS,IAAI,EAAE,OAAO;AACxB,UAAM,IAAI,MAAM,GAAG,MAAM,uBAAuB,EAAE,OAAO,iEAAiE;AAE5H,MAAI,EAAE,SAAS;AACb,UAAM,IAAI,MAAM,GAAG,MAAM,qMAAqM;AAIhO,MAAI,EAAE,iBAAiB;AACrB,UAAM,IAAI,MAAM,GAAG,MAAM,wIAAwI;AACrK;;;ADzPO,SAAS,gBAAgB,UAAkC,CAAC,GAAW;AAC5E,MAAI;AAGJ,MAAI,qBAAoC;AAExC,WAAS,UAAyB;AAChC,QAAI,uBAAuB;AACzB,aAAO;AACT,UAAM,UAAU,QAAQ,mBAAmBC,SAAQ,MAAM,cAAc;AACvE,QAAI;AACJ,QAAI;AACF,eAAS,YAAY,OAAO;AAAA,IAC9B,SACO,KAAK;AACV,UAAI,QAAQ;AACV,cAAM,IAAI,MAAM,0CAA0C,OAAO,KAAM,IAAc,OAAO,EAAE;AAChG,aAAO;AAAA,IACT;AAMA,wBAAoB,OAAO,KAAK,OAAO;AAOvC,UAAM,cAAc,gBAAgB,OAAO,KAAK,OAAO,SAAS,CAAC,QAAQ;AACvE,YAAM,IAAI,MAAM,GAAG;AAAA,IACrB,CAAC;AACD,yBAAqB,KAAK,UAAU,WAAW;AAC/C,WAAO;AAAA,EACT;AAKA,QAAM,aAAa,oBAAI,IAAY;AACnC,MAAI,WAA2B;AAC/B,QAAM,aAAa,MAAe;AAChC,QAAI,aAAa,MAAM;AACrB,UAAI;AACF,mBAAY,KAAK,MAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,iBAAkB;AAAA,MACtE,QACM;AACJ,mBAAW;AAAA,MACb;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,eAAe,QAAwB;AACrC,aAAO,OAAO;AACd,2BAAqB;AAAA,IACvB;AAAA,IACA,UAAU,MAAc,IAAY;AAClC,UAAI,eAAe,KAAK,EAAE,KAAK,CAAC,uBAAuB,KAAK,EAAE,KAAK,WAAW;AAC5E,eAAO;AACT,YAAM,UAAmC;AAAA,QACvC,CAAC,wBAAwB,iEAAiE;AAAA,QAC1F,CAAC,oCAAoC,uDAAuD;AAAA,QAC5F,CAAC,qBAAqB,yEAAyE;AAAA,QAC/F,CAAC,0BAA0B,iDAAiD;AAAA,QAC5E,CAAC,wBAAwB,oEAAoE;AAAA,MAC/F;AACA,iBAAW,CAAC,IAAI,GAAG,KAAK,SAAS;AAC/B,YAAI,GAAG,KAAK,IAAI,KAAK,CAAC,WAAW,IAAI,GAAG,GAAG;AACzC,qBAAW,IAAI,GAAG;AAClB,eAAK,KAAK,+CAA+C,GAAG,yLAAyL;AAAA,QACvP;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IACA,oBAAoB;AAAA,MAClB,OAAO;AAAA,MACP,QAAQ,MAAM;AACZ,cAAM,OAAO,QAAQ;AACrB,YAAI,SAAS;AACX,iBAAO;AACT,cAAM,WAAW,kBAAkB,IAAI;AACvC,cAAM,MAAM,8CAA8C,uBAAuB,KAAK,QAAQ;AAC9F,YAAI,KAAK,SAAS,QAAQ;AACxB,iBAAO,KAAK,QAAQ,UAAU;AAAA,MAAe,GAAG,EAAE;AACpD,eAAO,MAAM;AAAA,MACf;AAAA,IACF;AAAA,IACA,iBAAiB;AACf,YAAM,OAAO,QAAQ;AACrB,UAAI,SAAS;AACX;AACF,WAAK,SAAS;AAAA,QACZ,MAAM;AAAA,QACN,UAAU;AAAA,QACV,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;AExHA,SAAS,WAAAC,gBAAe;AAajB,SAAS,qBAAqB,UAAuC,CAAC,GAAW;AACtF,MAAI;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,eAAe,QAAwB;AACrC,aAAO,OAAO;AAAA,IAChB;AAAA,IACA,iBAAiB;AACf,YAAM,UAAU,QAAQ,mBAAmBC,SAAQ,MAAM,cAAc;AACvE,UAAI;AACJ,UAAI;AACF,iBAAS,YAAY,OAAO;AAAA,MAC9B,SACO,KAAK;AACV,YAAI,QAAQ;AACV,gBAAM,IAAI,MAAM,0CAA0C,OAAO,KAAM,IAAc,OAAO,EAAE;AAChG;AAAA,MACF;AAMA,+BAAyB,OAAO,KAAK,OAAO;AAI5C,YAAM,cAAc,gBAAgB,OAAO,KAAK,OAAO,SAAS,CAAC,QAAQ;AACvE,cAAM,IAAI,MAAM,GAAG;AAAA,MACrB,CAAC;AACD,WAAK,SAAS;AAAA,QACZ,MAAM;AAAA,QACN,UAAU;AAAA,QACV,QAAQ,KAAK,UAAU,WAAW;AAAA,MACpC,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;ACzBO,SAAS,sBAAsB,MAAkD;AACtF,SAAO,gBAAgB,GAAG,IAAI;AAChC;","names":["resolve","resolve","resolve","resolve"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wippy-fe/vite-plugin",
3
3
  "type": "module",
4
- "version": "0.0.64",
4
+ "version": "0.0.66",
5
5
  "description": "Vite plugins for Wippy apps and web components — package metadata injection, future build-time helpers.",
6
6
  "license": "UNLICENSED",
7
7
  "sideEffects": false,
@@ -16,7 +16,7 @@
16
16
  "vite": "^5 || ^6 || ^7"
17
17
  },
18
18
  "devDependencies": {
19
- "@wippy-fe/shared": "0.0.64"
19
+ "@wippy-fe/shared": "0.0.66"
20
20
  },
21
21
  "scripts": {
22
22
  "build": "tsup",