@opentray/ext-notification 0.30.1 → 0.30.2
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.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/platforms/darwin-arm64/libopentray_ext_notification.dylib +0 -0
- package/platforms/darwin-x64/libopentray_ext_notification.dylib +0 -0
- package/platforms/manifest.json +9 -9
- package/platforms/win32-arm64/opentray_ext_notification.dll +0 -0
- package/platforms/win32-x64/opentray_ext_notification.dll +0 -0
package/dist/index.mjs
CHANGED
|
@@ -254,10 +254,10 @@ function createNotificationCapability(context, platform) {
|
|
|
254
254
|
const native = requirePlatform();
|
|
255
255
|
const issue = validateNotifyOptions(options, native);
|
|
256
256
|
if (issue !== null) throw notificationErrorFromDescriptor(issue);
|
|
257
|
-
await
|
|
257
|
+
if ((await dispatch({
|
|
258
258
|
type: "notify",
|
|
259
259
|
...notifyCommandOptions(options, native)
|
|
260
|
-
});
|
|
260
|
+
})).kind === "immediate") return;
|
|
261
261
|
},
|
|
262
262
|
getAuthorizationStatus: async () => {
|
|
263
263
|
requirePlatform();
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/native-artifact.ts","../../spec/src/index.ts","../src/shared.ts","../src/index.ts"],"sourcesContent":["// Orthogonal intents (2026-09-18; add-ext-notification batch C):\n// 1. Declare the official notification platform library catalog as\n// platform-neutral data shipped inside this facade package (design\n// reference section 3, the archived dialog/sound embedded packaging law).\n// 2. Keep the embedded identity chain (facade package.json + contract.json)\n// relative to this facade instead of consumer cwd.\n\nimport type { NativeExtensionEmbeddedArtifact } from \"opentray\";\n\nexport const NOTIFICATION_NATIVE_ARTIFACT = {\n kind: \"embedded\",\n packageJsonUrl: new URL(\"../package.json\", import.meta.url).href,\n contractManifestUrl: new URL(\"../contract.json\", import.meta.url).href,\n targets: {\n \"darwin-arm64\": {\n libraryPath: \"platforms/darwin-arm64/libopentray_ext_notification.dylib\",\n },\n \"darwin-x64\": {\n libraryPath: \"platforms/darwin-x64/libopentray_ext_notification.dylib\",\n },\n \"win32-arm64\": {\n libraryPath: \"platforms/win32-arm64/opentray_ext_notification.dll\",\n },\n \"win32-x64\": {\n libraryPath: \"platforms/win32-x64/opentray_ext_notification.dll\",\n },\n },\n} satisfies NativeExtensionEmbeddedArtifact;\n","export type SessionId = string;\nexport type RequestId = string;\nexport type AppId = string;\nexport type TrayId = string;\nexport type MenuItemId = number;\n\n// Multi-webview orchestration, message-channel, and canonical JSON protocol\n// contracts (add-webview-orchestration); re-exported flat to keep one public\n// entry surface for consumers.\nexport * from \"./canonical-json\";\nexport * from \"./channel\";\nexport * from \"./webview\";\n\n/**\n * Protocol 2 (add-ext-dialog section 5.1) adds the DeferredOperation transaction\n * frames `ext-command-accepted` and `ext-operation-terminal`. The bump is one\n * matrix: Node client, broker, socket endpoint, and ready metadata all carry 2,\n * and a version-1 Init is rejected as incompatible (mirrors the Rust\n * `opentray-spec` PROTOCOL_VERSION truth).\n */\nexport const PROTOCOL_VERSION = 2;\nexport const OPENTRAY_PROTOCOL_FAMILY = \"opentray-protocol\";\n\n/**\n * Frozen wire form of a deferred operation id: exactly 16 lowercase hex\n * digits (the u64 handle's hex projection, add-ext-dialog design reference\n * section 5.7 ruling #2). Shared parser truth for `ext-command-accepted` and\n * `ext-operation-terminal`; empty, uppercase, non-hex, or wrong-length ids\n * are structurally invalid.\n */\nexport const OPERATION_ID_PATTERN = /^[0-9a-f]{16}$/u;\n\n/** Returns true when an unknown value matches the frozen 16-hex operation id form. */\nexport const isOperationId = (value: unknown): value is string =>\n typeof value === \"string\" && OPERATION_ID_PATTERN.test(value);\nexport const OPENTRAY_PROTOCOL_LINE_MAJOR = 1;\nexport const OPENTRAY_PROTOCOL_LINE_MINOR = 1;\n\nexport const protocolLineReleaseChannels = [\"stable\", \"alpha\"] as const;\nexport type ProtocolLineReleaseChannel = (typeof protocolLineReleaseChannels)[number];\n\nexport interface OpenTrayProtocolLine {\n family: typeof OPENTRAY_PROTOCOL_FAMILY;\n major: number;\n minor: number;\n}\n\nexport interface ProtocolDistTagOptions {\n channel: ProtocolLineReleaseChannel;\n major?: number;\n minor?: number;\n}\n\nexport interface ProtocolDistTag {\n channel: ProtocolLineReleaseChannel;\n major: number;\n minor: number;\n}\n\nexport const OPENTRAY_PROTOCOL_LINE: OpenTrayProtocolLine = {\n family: OPENTRAY_PROTOCOL_FAMILY,\n major: OPENTRAY_PROTOCOL_LINE_MAJOR,\n minor: OPENTRAY_PROTOCOL_LINE_MINOR,\n};\n\n// Install-time protocol lines can advance by minor version while runtime authority stays numeric.\nexport const compareOpenTrayProtocolLine = (\n left: OpenTrayProtocolLine,\n right: OpenTrayProtocolLine,\n): number => {\n assertOpenTrayProtocolLine(left, \"left\");\n assertOpenTrayProtocolLine(right, \"right\");\n if (left.major !== right.major) {\n return left.major - right.major;\n }\n return left.minor - right.minor;\n};\n\nexport const isOpenTrayProtocolLineCompatible = (\n supported: OpenTrayProtocolLine,\n required: OpenTrayProtocolLine,\n): boolean => {\n assertOpenTrayProtocolLine(supported, \"supported\");\n assertOpenTrayProtocolLine(required, \"required\");\n return supported.major === required.major && supported.minor >= required.minor;\n};\n\nexport const formatOpenTrayProtocolLine = ({\n family,\n major,\n minor,\n}: OpenTrayProtocolLine = OPENTRAY_PROTOCOL_LINE): string => {\n assertProtocolLineVersion(major, \"major\");\n assertProtocolLineVersion(minor, \"minor\");\n return `${family}/${major}.${minor}`;\n};\n\nexport const isProtocolLineReleaseChannel = (value: string): value is ProtocolLineReleaseChannel =>\n protocolLineReleaseChannels.includes(value as ProtocolLineReleaseChannel);\n\nexport const formatProtocolDistTag = ({\n channel,\n major = OPENTRAY_PROTOCOL_LINE_MAJOR,\n minor = OPENTRAY_PROTOCOL_LINE_MINOR,\n}: ProtocolDistTagOptions): string => {\n assertProtocolLineReleaseChannel(channel);\n assertProtocolLineVersion(major, \"major\");\n assertProtocolLineVersion(minor, \"minor\");\n return `${channel}-${major}-${minor}`;\n};\n\nexport const parseProtocolDistTag = (tag: string): ProtocolDistTag => {\n const match = /^(?<channel>[a-z]+)-(?<major>\\d+)-(?<minor>\\d+)$/u.exec(tag);\n const groups = match?.groups;\n if (groups === undefined) {\n throw new Error(`invalid OpenTray protocol dist-tag: ${tag}`);\n }\n const channel = groups.channel;\n if (channel === undefined || !isProtocolLineReleaseChannel(channel)) {\n throw new Error(`unsupported OpenTray protocol dist-tag channel: ${channel ?? \"\"}`);\n }\n const major = Number(groups.major);\n const minor = Number(groups.minor);\n assertProtocolLineVersion(major, \"major\");\n assertProtocolLineVersion(minor, \"minor\");\n return { channel, major, minor };\n};\n\n/**\n * Neutral caller label used when no usable caller identity can be derived.\n * Keeps the broker honest instead of impersonating an unrelated application.\n */\nexport const DEFAULT_CALLER_LABEL = \"opentray\";\n\n/**\n * Maximum length of a sanitized caller label. Keeps socket paths, runtime\n * directory names, and process titles within platform limits.\n */\nexport const CALLER_LABEL_MAX_LENGTH = 48;\n\nconst callerLabelAllowedPattern = /[a-z0-9-]+/g;\n\nexport interface BrokerEndpointIdentity {\n packageVersion: string;\n protocolVersion: number;\n callerLabel: string;\n}\n\nexport interface BrokerEndpointIdentityOptions {\n packageVersion: string;\n protocolVersion?: number;\n callerLabel?: string;\n}\n\nexport const sanitizeCallerLabel = (value: string | undefined): string => {\n if (value !== undefined && typeof value !== \"string\") {\n throw new Error(`callerLabel must be a string: ${String(value)}`);\n }\n const raw = value ?? \"\";\n const lowered = raw.toLowerCase();\n const segments = lowered.match(callerLabelAllowedPattern);\n if (segments === null || segments.length === 0) {\n return DEFAULT_CALLER_LABEL;\n }\n const joined = segments.join(\"-\");\n const trimmed = joined.replace(/^-+|-+$/gu, \"\");\n if (trimmed.length === 0) {\n return DEFAULT_CALLER_LABEL;\n }\n return trimmed.slice(0, CALLER_LABEL_MAX_LENGTH);\n};\n\nexport const createBrokerEndpointIdentity = ({\n packageVersion,\n protocolVersion = PROTOCOL_VERSION,\n callerLabel,\n}: BrokerEndpointIdentityOptions): BrokerEndpointIdentity => {\n assertEndpointComponent(packageVersion, \"packageVersion\");\n if (!Number.isInteger(protocolVersion) || protocolVersion <= 0) {\n throw new Error(`protocolVersion must be a positive integer: ${protocolVersion}`);\n }\n\n return {\n packageVersion,\n protocolVersion,\n callerLabel: sanitizeCallerLabel(callerLabel),\n };\n};\n\nexport const isSupportedProtocolVersion = (protocolVersion: number): boolean =>\n protocolVersion === PROTOCOL_VERSION;\n\nexport const formatBrokerEndpointName = (identity: BrokerEndpointIdentity): string => {\n assertEndpointIdentity(identity);\n return `opentray-${identity.packageVersion}-p${identity.protocolVersion}-${identity.callerLabel}`;\n};\n\nexport const formatBrokerStateRoot = (\n homeDir: string,\n identity: BrokerEndpointIdentity,\n): string => {\n assertEndpointIdentity(identity);\n if (homeDir.length === 0) {\n throw new Error(\"homeDir must not be empty\");\n }\n\n const normalizedHome = homeDir.replace(/[\\\\/]+$/u, \"\");\n return `${normalizedHome}/.opentray/${identity.packageVersion}/${identity.callerLabel}`;\n};\n\nexport const formatUnixSocketPath = (homeDir: string, identity: BrokerEndpointIdentity): string =>\n `${formatBrokerStateRoot(homeDir, identity)}/opentray-p${identity.protocolVersion}.sock`;\n\nexport const formatWindowsPipeName = (identity: BrokerEndpointIdentity): string =>\n `\\\\\\\\.\\\\pipe\\\\${formatBrokerEndpointName(identity)}`;\n\n/**\n * Human-readable process title for a broker pinned to a caller. Used by the SDK\n * spawn path so task managers show the owning application, not a generic name.\n */\nexport const formatBrokerProcessTitle = (identity: BrokerEndpointIdentity): string => {\n assertEndpointIdentity(identity);\n return `opentray · ${identity.callerLabel}`;\n};\n\nconst endpointComponentPattern = /^[0-9A-Za-z._+-]+$/u;\n\nconst assertOpenTrayProtocolLine = (line: OpenTrayProtocolLine, name: string): void => {\n assertProtocolLineVersion(line.major, `${name}.major`);\n assertProtocolLineVersion(line.minor, `${name}.minor`);\n};\n\nconst assertProtocolLineReleaseChannel = (channel: string): void => {\n if (!isProtocolLineReleaseChannel(channel)) {\n throw new Error(`unsupported OpenTray protocol release channel: ${channel}`);\n }\n};\n\nconst assertProtocolLineVersion = (value: number, name: string): void => {\n if (!Number.isInteger(value) || value < 0) {\n throw new Error(`protocol line ${name} must be a non-negative integer: ${value}`);\n }\n};\n\nconst assertEndpointIdentity = (identity: BrokerEndpointIdentity): void => {\n assertEndpointComponent(identity.packageVersion, \"packageVersion\");\n if (!Number.isInteger(identity.protocolVersion) || identity.protocolVersion <= 0) {\n throw new Error(`protocolVersion must be a positive integer: ${identity.protocolVersion}`);\n }\n};\n\nconst assertEndpointComponent = (value: string, name: string): void => {\n if (value.length === 0) {\n throw new Error(`${name} must not be empty`);\n }\n if (!endpointComponentPattern.test(value)) {\n throw new Error(`${name} contains invalid endpoint characters: ${value}`);\n }\n};\n\nexport interface AppOptions {\n id?: AppId;\n name?: string;\n appIcon?: AppIcon;\n default?: boolean;\n}\n\nexport interface AppRef {\n appId: AppId;\n}\n\nexport interface AppIdentity {\n appId: AppId;\n appName: string;\n appIcon?: AppIcon;\n appIconVariant?: string;\n}\n\nexport interface TrayOptions {\n id: string;\n tooltip?: Tooltip;\n icon?: Icon;\n menu?: Menu;\n}\n\nexport interface Tooltip {\n title: string;\n description: string;\n}\n\nexport interface Menu {\n items: MenuItem[];\n}\n\nexport type MenuItem =\n | {\n type: \"item\";\n id: MenuItemId;\n title: string;\n primaryEvent?: boolean;\n enabled?: boolean;\n shortcut?: string;\n }\n | {\n type: \"check\";\n id: MenuItemId;\n title: string;\n enabled?: boolean;\n checked?: boolean;\n }\n | {\n type: \"radio\";\n id: MenuItemId;\n title: string;\n enabled?: boolean;\n checked?: boolean;\n group: number;\n }\n | {\n type: \"separator\";\n }\n | {\n type: \"submenu\";\n title: string;\n enabled?: boolean;\n items: MenuItem[];\n };\n\nexport type IconImage =\n | { type: \"rgba\"; data: Uint8Array | number[]; width: number; height: number }\n | { type: \"encoded\"; data: Uint8Array | number[] }\n | { type: \"file\"; path: string };\n\n/** A source carrying one platform-native application icon file. */\nexport type AppIconSource =\n | { type: \"encoded\"; data: Uint8Array | readonly number[] }\n | { type: \"file\"; path: string };\n\n/** One or more application-defined semantic names for a native App icon asset. */\nexport type AppIconVariant = string | readonly string[];\n\ninterface AppIconVariantField {\n /** Omission is equivalent to the canonical `default` variant. */\n variant?: AppIconVariant;\n}\n\n/** One standards-compliant application icon asset for a specific operating system. */\nexport type AppIconAsset = AppIconVariantField &\n (\n | { platform: \"darwin\"; format: \"icns\"; source: AppIconSource }\n | { platform: \"windows\"; format: \"ico\"; source: AppIconSource }\n | {\n platform: \"linux\";\n format: \"png\";\n size: number;\n source: AppIconSource;\n }\n | { platform: \"linux\"; format: \"svg\"; source: AppIconSource }\n );\n\n/** Cross-platform application identity assets; tray `Icon` is a separate contract. */\nexport type AppIcon = readonly AppIconAsset[];\n\ntype AppIconAssetVariantName<TAsset> = TAsset extends {\n readonly variant: infer TVariant;\n}\n ? TVariant extends readonly string[]\n ? TVariant[number]\n : TVariant extends string\n ? TVariant\n : never\n : never;\n\n/** Literal variant names declared by an `as const` AppIcon catalog. */\nexport type AppIconVariantOf<TCatalog extends AppIcon> =\n | \"default\"\n | AppIconAssetVariantName<TCatalog[number]>;\n\nexport type SimpleIcon = IconImage & { text?: string };\n\nexport type IconText = IconImage & { text: string };\n\nexport type DarwinIcon = IconImage & { isTemplate?: boolean };\n\nexport type Win32Icon = IconImage;\n\nexport type LinuxIcon = IconImage;\n\nexport type DarwinIconText = DarwinIcon & { text: string };\n\nexport interface IconCandidates {\n \"icon-only\"?: IconImage;\n \"text-only\"?: string;\n \"icon-text\"?: IconText;\n \"darwin-icon-only\"?: DarwinIcon;\n \"darwin-icon-text\"?: DarwinIconText;\n \"win32-icon-only\"?: Win32Icon;\n \"win32-icon-text\"?: IconText;\n \"linux-icon-only\"?: LinuxIcon;\n \"linux-icon-text\"?: IconText;\n}\n\nexport type Icon = IconCandidates & Partial<SimpleIcon>;\n\nexport interface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport type TrayBoundsKind = \"native\" | \"inferred\" | \"unavailable\";\n\nexport interface TrayBoundsResult {\n kind: TrayBoundsKind;\n source: string;\n rect: Rect | null;\n}\n\nexport type MouseButton = \"left\" | \"right\" | \"middle\";\n\n/** Application-scoped lifecycle intent emitted by a native runtime carrier. */\nexport type AppEvent = { type: \"reopenRequested\"; appId: AppId };\n\nexport type TrayEvent =\n | { type: \"ready\"; appId: AppId }\n | { type: \"menuClick\"; appId: AppId; trayId: TrayId; itemId: MenuItemId }\n | {\n type: \"trayClick\";\n appId: AppId;\n trayId: TrayId;\n button: MouseButton;\n x: number;\n y: number;\n }\n | {\n type: \"trayDoubleClick\";\n appId: AppId;\n trayId: TrayId;\n button: MouseButton;\n x: number;\n y: number;\n };\n\nexport interface ExtensionScope {\n appId: AppId;\n trayId?: TrayId;\n ext: string;\n}\n\n/**\n * Broker-injected command ownership (add-ext-dialog section 5.5): the host derives\n * `{ appId, trayId, sessionId, instanceGeneration }` for every command\n * dispatch and extensions must never self-report it. All busy/operation\n * registries key on this scope; the deferred operation binding is\n * `(sessionId, instanceGeneration, operationId)`.\n */\nexport interface CommandScope {\n appId: AppId;\n trayId: TrayId;\n sessionId: SessionId;\n instanceGeneration: number;\n}\n\nexport interface ExtensionEnvelope<TData = unknown> {\n scope: ExtensionScope;\n /**\n * Present only on the command dispatch path: the broker-injected ownership\n * scope for this invocation. Event envelopes never carry it.\n */\n commandScope?: CommandScope;\n data: TData;\n}\n\n/**\n * Typed extension error envelope (add-ext-dialog section 7.5): `{ code, message,\n * details }` with a discriminated `details` JSON shape shared by the Rust\n * `ExtensionError::Detailed` projection, server error frames, and the Node\n * typed error factory. Consumers must match on `code`; parsing the human\n * `message` is forbidden by contract.\n */\nexport interface TypedExtensionError {\n code: string;\n message: string;\n /** Discriminated JSON payload whose shape each error code freezes. Absent for codes without structured detail. */\n details?: unknown;\n}\n\n/**\n * Terminal payload of a deferred operation (add-ext-dialog section 5.1 frozen): the\n * `result` branch resolves the client promise with `value`; the `error`\n * branch rejects it with a typed extension error. The cancel path is a\n * `result` payload isomorphic to user cancellation — there is no third\n * channel.\n */\nexport type ExtOperationPayload =\n | { kind: \"result\"; value: unknown }\n | { kind: \"error\"; error: TypedExtensionError };\n\n/**\n * Shared ingress bound for one extension event/terminal record (64 KiB). The\n * value is the single source of truth for both the EventPort record bound and\n * the DeferredPort terminal payload bound; the Rust `opentray-spec` crate\n * exports the same number (Rust/TS fixture parity).\n */\nexport const EXTENSION_EVENT_RECORD_MAX_BYTES = 64 * 1024;\n\nexport interface ExtensionArtifactTarget {\n os: string;\n arch: string;\n}\n\nexport interface ExpectedExtensionIdentity {\n extensionName: string;\n artifactSetVersion: string;\n contractFingerprint: string;\n target: ExtensionArtifactTarget;\n /**\n * Optional embedded-artifact identity-chain inputs (add-ext-dialog section 6.4):\n * lowercase hex SHA-256 of the resolved library file, verified by the broker\n * before `dlopen`. Absent means the caller supplied no byte hash\n * (registry-era identity only).\n */\n sha256?: string;\n /**\n * Optional build identity carried by the embedded staging manifest; the\n * broker compares it against the native manifest between `Library::new` and\n * `init` when provided.\n */\n buildIdentity?: string;\n}\n\n/** Operating-system and architecture identity for one broker executable. */\nexport interface BrokerArtifactTarget {\n os: \"darwin\" | \"linux\" | \"win32\";\n arch: \"arm64\" | \"x64\";\n}\n\n/** Content-derived identity used to decide whether a live broker may be reused. */\nexport interface BrokerArtifactIdentity {\n packageVersion: string;\n target: BrokerArtifactTarget;\n executableHash: string;\n buildIdentity: string;\n}\n\n/** Caller-scoped readiness evidence written by a successfully initialized broker. */\nexport interface BrokerReadyMetadata {\n pid: number;\n endpoint: string;\n packageVersion: string;\n protocolVersion: number;\n appId: AppId;\n appName: string;\n callerLabel: string;\n executablePath: string;\n brokerArtifactIdentity: BrokerArtifactIdentity;\n}\n\nexport interface RuntimeHostSessionHealth {\n sessionId: number;\n internalSessionId?: SessionId;\n initialized: boolean;\n}\n\nexport interface RuntimeHostHealth {\n pid: number;\n packageVersion: string;\n protocolVersion: number;\n endpoint: string;\n appId: AppId;\n appName: string;\n callerLabel: string;\n sessionCount: number;\n sessions: RuntimeHostSessionHealth[];\n}\n\nexport type ClientFrame =\n | { type: \"init\"; protocolVersion: number; clientVersion: string }\n | ClientRequestFrame\n | { type: \"exit\" };\n\nexport type ClientRequestFrame =\n | ({ type: \"create-app\"; requestId: RequestId } & AppOptions)\n | { type: \"resolve-default-app\"; requestId: RequestId }\n | { type: \"get-app-identity\"; requestId: RequestId; appId: AppId }\n | {\n type: \"set-app-name\";\n requestId: RequestId;\n appId: AppId;\n name: string;\n }\n | {\n type: \"set-app-icon\";\n requestId: RequestId;\n appId: AppId;\n appIcon: AppIcon | null;\n }\n | {\n type: \"set-app-icon-variant\";\n requestId: RequestId;\n appId: AppId;\n variant: string;\n }\n | {\n type: \"create-tray\";\n requestId: RequestId;\n app: AppRef;\n tray: TrayOptions;\n }\n | { type: \"destroy-tray\"; requestId: RequestId; appId: AppId; trayId: TrayId }\n | {\n type: \"get-tray-bounds\";\n requestId: RequestId;\n appId: AppId;\n trayId: TrayId;\n }\n | {\n type: \"set-tray-menu\";\n requestId: RequestId;\n appId: AppId;\n trayId: TrayId;\n menu: Menu;\n }\n | {\n type: \"set-tray-icon\";\n requestId: RequestId;\n appId: AppId;\n trayId: TrayId;\n icon: Icon;\n }\n | {\n type: \"set-tray-tooltip\";\n requestId: RequestId;\n appId: AppId;\n trayId: TrayId;\n tooltip: Tooltip;\n }\n | {\n type: \"load-ext\";\n requestId: RequestId;\n appId: AppId;\n name: string;\n path: string;\n expectedIdentity: ExpectedExtensionIdentity;\n mountId?: string;\n }\n | {\n type: \"ext-command\";\n requestId: RequestId;\n appId: AppId;\n trayId: TrayId;\n ext: string;\n data: unknown;\n }\n | { type: \"unload-ext\"; requestId: RequestId; appId: AppId; name: string }\n | { type: \"health\"; requestId: RequestId };\n\nexport type ServerFrame =\n | {\n type: \"ready\";\n protocolVersion: number;\n brokerVersion: string;\n brokerArtifactIdentity: BrokerArtifactIdentity;\n sessionId: SessionId;\n }\n | { type: \"app-created\"; requestId: RequestId; app: AppRef }\n | { type: \"default-app\"; requestId: RequestId; app: AppRef }\n | {\n type: \"app-identity\";\n requestId: RequestId;\n identity: AppIdentity;\n }\n | { type: \"tray-created\"; requestId: RequestId; appId: AppId; trayId: TrayId }\n | {\n type: \"tray-bounds\";\n requestId: RequestId;\n appId: AppId;\n trayId: TrayId;\n bounds: TrayBoundsResult;\n }\n | { type: \"ack\"; requestId: RequestId }\n | {\n type: \"ext-command-result\";\n requestId: RequestId;\n events: ExtensionEnvelope[];\n }\n | {\n /**\n * DeferredOperation acceptance (section 5.3): the command did not complete\n * inside the dispatch; the client keeps the request pending until the\n * matching `ext-operation-terminal` (or a transport-close rejection).\n */\n type: \"ext-command-accepted\";\n requestId: RequestId;\n operationId: string;\n }\n | {\n /**\n * The single terminal frame of a deferred operation. Exactly one is\n * delivered per accepted operation; duplicates are dropped by the\n * broker's owner loop before any frame is written.\n */\n type: \"ext-operation-terminal\";\n operationId: string;\n payload: ExtOperationPayload;\n }\n | {\n type: \"runtime-host-health\";\n requestId: RequestId;\n health: RuntimeHostHealth;\n }\n | { type: \"event\"; event: TrayEvent }\n | { type: \"app-event\"; event: AppEvent }\n | {\n type: \"ext-event\";\n appId: AppId;\n trayId: TrayId;\n ext: string;\n data: unknown;\n }\n | {\n /**\n * Synchronous broker error frame. Carries the same typed error envelope\n * as a deferred terminal's `error` branch (add-ext-dialog design\n * reference section 7.5): when the error code freezes a structured\n * payload it rides `details` as a JSON object; the field is absent for\n * codes without structured detail (never `null`).\n */\n type: \"error\";\n requestId?: RequestId;\n code: string;\n message: string;\n details?: unknown;\n };\n\nexport interface ParseResult<T> {\n ok: boolean;\n frame?: T;\n error?: string;\n}\n\nexport const parseServerFrame = (line: string): ParseResult<ServerFrame> => {\n try {\n const value: unknown = JSON.parse(line);\n if (!isServerFrame(value)) {\n return { ok: false, error: \"invalid server frame\" };\n }\n return { ok: true, frame: value };\n } catch (error) {\n return {\n ok: false,\n error: error instanceof Error ? error.message : \"unknown parse error\",\n };\n }\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\n/** Returns true when an unknown value is a complete broker artifact identity. */\nexport const isBrokerArtifactIdentity = (value: unknown): value is BrokerArtifactIdentity =>\n isRecord(value) &&\n typeof value.packageVersion === \"string\" &&\n isRecord(value.target) &&\n (value.target.os === \"darwin\" || value.target.os === \"linux\" || value.target.os === \"win32\") &&\n (value.target.arch === \"arm64\" || value.target.arch === \"x64\") &&\n typeof value.executableHash === \"string\" &&\n /^[a-f0-9]{64}$/u.test(value.executableHash) &&\n typeof value.buildIdentity === \"string\" &&\n value.buildIdentity.length > 0;\n\n/** Compares every compatibility-bearing broker artifact identity field. */\nexport const brokerArtifactIdentityEquals = (\n left: BrokerArtifactIdentity,\n right: BrokerArtifactIdentity,\n): boolean =>\n left.packageVersion === right.packageVersion &&\n left.target.os === right.target.os &&\n left.target.arch === right.target.arch &&\n left.executableHash === right.executableHash &&\n left.buildIdentity === right.buildIdentity;\n\nexport const isServerFrame = (value: unknown): value is ServerFrame => {\n if (!isRecord(value) || typeof value.type !== \"string\") {\n return false;\n }\n\n switch (value.type) {\n case \"ready\":\n return (\n typeof value.protocolVersion === \"number\" &&\n typeof value.brokerVersion === \"string\" &&\n isBrokerArtifactIdentity(value.brokerArtifactIdentity) &&\n typeof value.sessionId === \"string\"\n );\n case \"app-created\":\n return typeof value.requestId === \"string\" && isRecord(value.app);\n case \"default-app\":\n return typeof value.requestId === \"string\" && isRecord(value.app);\n case \"app-identity\":\n return typeof value.requestId === \"string\" && isRecord(value.identity);\n case \"tray-created\":\n return (\n typeof value.requestId === \"string\" &&\n typeof value.appId === \"string\" &&\n typeof value.trayId === \"string\"\n );\n case \"tray-bounds\":\n return (\n typeof value.requestId === \"string\" &&\n typeof value.appId === \"string\" &&\n typeof value.trayId === \"string\" &&\n isTrayBoundsResult(value.bounds)\n );\n case \"ack\":\n return typeof value.requestId === \"string\";\n case \"ext-command-result\":\n return (\n typeof value.requestId === \"string\" &&\n Array.isArray(value.events) &&\n value.events.every(isExtensionEnvelope)\n );\n case \"ext-command-accepted\":\n return (\n typeof value.requestId === \"string\" && isOperationId(value.operationId)\n );\n case \"ext-operation-terminal\":\n return isOperationId(value.operationId) && isExtOperationPayload(value.payload);\n case \"runtime-host-health\":\n return typeof value.requestId === \"string\" && isRuntimeHostHealth(value.health);\n case \"event\":\n return isTrayEvent(value.event);\n case \"app-event\":\n return isAppEvent(value.event);\n case \"ext-event\":\n return (\n typeof value.appId === \"string\" &&\n typeof value.trayId === \"string\" &&\n typeof value.ext === \"string\"\n );\n case \"error\":\n return (\n (value.requestId === undefined || typeof value.requestId === \"string\") &&\n typeof value.code === \"string\" &&\n typeof value.message === \"string\" &&\n // details is a JSON object when present (absent for codes without\n // structured detail); nulls and scalars are structurally invalid.\n (value.details === undefined || isRecord(value.details))\n );\n default:\n return false;\n }\n};\n\nconst isAppEvent = (value: unknown): value is AppEvent =>\n isRecord(value) &&\n value.type === \"reopenRequested\" &&\n typeof value.appId === \"string\";\n\nconst isExtensionEnvelope = (value: unknown): value is ExtensionEnvelope => {\n if (!isRecord(value) || !isRecord(value.scope)) {\n return false;\n }\n const scope = value.scope as Record<string, unknown>;\n return (\n typeof scope.appId === \"string\" &&\n (scope.trayId === undefined || typeof scope.trayId === \"string\") &&\n typeof scope.ext === \"string\" &&\n (value.commandScope === undefined || isCommandScope(value.commandScope)) &&\n \"data\" in value\n );\n};\n\n/** Returns true when an unknown value is a complete broker-injected command scope. */\nexport const isCommandScope = (value: unknown): value is CommandScope =>\n isRecord(value) &&\n typeof value.appId === \"string\" &&\n typeof value.trayId === \"string\" &&\n typeof value.sessionId === \"string\" &&\n typeof value.instanceGeneration === \"number\" &&\n Number.isInteger(value.instanceGeneration) &&\n value.instanceGeneration >= 0;\n\n/** Returns true when an unknown value is a complete typed extension error envelope. */\nexport const isTypedExtensionError = (value: unknown): value is TypedExtensionError =>\n isRecord(value) &&\n typeof value.code === \"string\" &&\n typeof value.message === \"string\" &&\n // details is a JSON object when present (absent for codes without\n // structured detail); nulls, scalars, and arrays are structurally invalid\n // on the synchronous error frame and on deferred terminal errors alike.\n (value.details === undefined || isRecord(value.details));\n\n/** Parser truth for the frozen terminal payload discriminant (no third branch is guessed). */\nexport const isExtOperationPayload = (value: unknown): value is ExtOperationPayload => {\n if (!isRecord(value)) {\n return false;\n }\n switch (value.kind) {\n case \"result\":\n return \"value\" in value;\n case \"error\":\n return isTypedExtensionError(value.error);\n default:\n return false;\n }\n};\n\nconst isRuntimeHostHealth = (value: unknown): value is RuntimeHostHealth => {\n if (!isRecord(value)) {\n return false;\n }\n\n return (\n typeof value.pid === \"number\" &&\n typeof value.packageVersion === \"string\" &&\n typeof value.protocolVersion === \"number\" &&\n typeof value.endpoint === \"string\" &&\n typeof value.appId === \"string\" &&\n typeof value.appName === \"string\" &&\n typeof value.callerLabel === \"string\" &&\n typeof value.sessionCount === \"number\" &&\n Array.isArray(value.sessions) &&\n value.sessions.every(isRuntimeHostSessionHealth)\n );\n};\n\nconst isRuntimeHostSessionHealth = (value: unknown): value is RuntimeHostSessionHealth =>\n isRecord(value) &&\n typeof value.sessionId === \"number\" &&\n (value.internalSessionId === undefined || typeof value.internalSessionId === \"string\") &&\n typeof value.initialized === \"boolean\";\n\nconst isRect = (value: unknown): value is Rect =>\n isRecord(value) &&\n typeof value.x === \"number\" &&\n typeof value.y === \"number\" &&\n typeof value.width === \"number\" &&\n typeof value.height === \"number\";\n\nconst isTrayBoundsResult = (value: unknown): value is TrayBoundsResult =>\n isRecord(value) &&\n (value.kind === \"native\" || value.kind === \"inferred\" || value.kind === \"unavailable\") &&\n typeof value.source === \"string\" &&\n (value.rect === null || isRect(value.rect));\n\nconst isTrayEvent = (value: unknown): value is TrayEvent => {\n if (!isRecord(value) || typeof value.type !== \"string\") {\n return false;\n }\n\n switch (value.type) {\n case \"ready\":\n return typeof value.appId === \"string\";\n case \"menuClick\":\n return (\n typeof value.appId === \"string\" &&\n typeof value.trayId === \"string\" &&\n typeof value.itemId === \"number\"\n );\n case \"trayClick\":\n case \"trayDoubleClick\":\n return (\n typeof value.appId === \"string\" &&\n typeof value.trayId === \"string\" &&\n isMouseButton(value.button) &&\n typeof value.x === \"number\" &&\n typeof value.y === \"number\"\n );\n default:\n return false;\n }\n};\n\nconst isMouseButton = (value: unknown): value is MouseButton =>\n value === \"left\" || value === \"right\" || value === \"middle\";\n\n// ---------------------------------------------------------------------------\n// Sound extension shared schema (add-ext-sound design reference sections 1-2,\n// task 2.1): the schema is promoted to this shared spec so `@opentray/ext-sound`\n// and the `opentray-spec` crate project the same DTO truth. The frozen\n// common-name table and the typed error family live here so facade, native,\n// and broker rejections stay code-identical across the wire.\n// ---------------------------------------------------------------------------\n\n/**\n * Alert-grade beep kinds (`beep` only, add-ext-sound design reference section\n * 1.1). Alert grading and named system sounds are two separate semantic\n * layers: `default`/`info`/`question` never enter the common system sound\n * catalog.\n */\nexport type BeepKind = \"default\" | \"info\" | \"warning\" | \"error\" | \"question\";\n\n/**\n * Common system sound names (add-ext-sound R1 section 4.1, frozen catalog:\n * no additions or removals in v1).\n */\nexport type CommonSystemSoundName = \"notification\" | \"warning\" | \"error\";\n\n/** Frozen common-name catalog in wire order; the facade consults it before any native passthrough. */\nexport const COMMON_SYSTEM_SOUND_NAMES: readonly CommonSystemSoundName[] = [\n \"notification\",\n \"warning\",\n \"error\",\n];\n\n/**\n * Frozen per-platform native projection of each common name (add-ext-sound\n * design reference section 1.2): darwin `NSSound` system catalog names and\n * win32 registry sound-scheme aliases (`SND_ALIAS`). Both records are frozen;\n * a changed projection is a contract change, not a data edit.\n */\nexport const COMMON_SYSTEM_SOUND_PROJECTIONS: Readonly<\n Record<CommonSystemSoundName, { readonly darwin: string; readonly win32: string }>\n> = Object.freeze({\n notification: Object.freeze({ darwin: \"Glass\", win32: \"SystemAsterisk\" }),\n warning: Object.freeze({ darwin: \"Sosumi\", win32: \"SystemExclamation\" }),\n error: Object.freeze({ darwin: \"Basso\", win32: \"SystemHand\" }),\n});\n\n/** Returns true when an unknown value is one of the frozen common system sound names. */\nexport const isCommonSystemSoundName = (\n value: string\n): value is CommonSystemSoundName =>\n (COMMON_SYSTEM_SOUND_NAMES as readonly string[]).includes(value);\n\n/**\n * A common name (typed union with autocomplete) or any platform-native sound\n * name (arbitrary string, resolved at runtime against the platform catalog).\n */\nexport type SystemSoundName = CommonSystemSoundName | (string & {});\n\n/**\n * v1 deliberately carries no options (add-ext-sound design reference section\n * 1.3 ruling: the rejection path already excluded volume control). Reserved\n * empty interface — future fields land in platform namespaces first (dialog\n * law), never as silently ignored values.\n */\nexport interface PlaySoundOptions {\n // Intentionally empty in v1.\n}\n\n/**\n * Shared sound backend capabilities DTO (add-ext-sound design reference\n * section 2). `fileFormats` is the v1 committed set (finite canonical:\n * darwin `[\"wav\",\"aiff\",\"mp3\",\"m4a\"]`, win32 `[\"wav\"]`), not a runtime-open\n * catalog of whatever the OS currently accepts.\n */\nexport interface SoundBackendCapabilities {\n platform: \"darwin\" | \"win32\";\n /** Platform-native sound-name catalog resolvable (`playSystemSound` passthrough). */\n systemSoundCatalog: boolean;\n /** File playback available (`playSound`). */\n playFile: boolean;\n fileFormats: readonly string[];\n}\n\n/** Returns true when an unknown value is a complete sound backend capabilities DTO. */\nexport const isSoundBackendCapabilities = (\n value: unknown\n): value is SoundBackendCapabilities => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n const record = value as Record<string, unknown>;\n if (record.platform !== \"darwin\" && record.platform !== \"win32\") {\n return false;\n }\n return (\n typeof record.systemSoundCatalog === \"boolean\" &&\n typeof record.playFile === \"boolean\" &&\n Array.isArray(record.fileFormats) &&\n record.fileFormats.every((format) => typeof format === \"string\")\n );\n};\n\n/** Typed sound error family (add-ext-sound design reference section 3, frozen at four codes). */\nexport const SOUND_ERROR_CODES = {\n platformUnsupported: \"sound_platform_unsupported\",\n notFound: \"sound_not_found\",\n formatUnsupported: \"sound_format_unsupported\",\n fileUnreadable: \"sound_file_unreadable\",\n} as const;\n\nexport type SoundErrorCode = (typeof SOUND_ERROR_CODES)[keyof typeof SOUND_ERROR_CODES];\n\nconst SOUND_ERROR_CODE_VALUES = new Set<string>(Object.values(SOUND_ERROR_CODES));\n\n/** Returns true when an unknown value is one of the frozen typed sound error codes. */\nexport const isSoundErrorCode = (value: string): value is SoundErrorCode =>\n SOUND_ERROR_CODE_VALUES.has(value);\n\n/** How a `sound_not_found` miss was searched, in resolution order (common table first). */\nexport type SystemSoundAttemptedMode = \"common-table\" | \"platform-catalog\";\n\n/**\n * Discriminated details payloads for the typed sound error family\n * (add-ext-sound design reference section 1.2: a miss never stays silent and\n * never falls back — `sound_not_found` details carry at least the requested\n * name, the platform, and the attempted modes/catalog).\n */\nexport type SoundErrorDetails =\n | { kind: \"platform\"; platform: string }\n | {\n kind: \"not-found\";\n requested: string;\n platform: \"darwin\" | \"win32\";\n attempted: readonly SystemSoundAttemptedMode[];\n }\n | { kind: \"format\"; path: string; reason: string }\n | { kind: \"unreadable\"; path: string };\n\n// ---------------------------------------------------------------------------\n// Clipboard extension shared schema (add-ext-clipboard design reference)\n// ---------------------------------------------------------------------------\n\n/** Frozen clipboard write bound: 1 MiB = 1,048,576 UTF-16 code units. */\nexport const CLIPBOARD_MAX_WRITE_UTF16 = 1_048_576;\n\n/** Shared clipboard backend capabilities DTO (add-ext-clipboard design reference). */\nexport interface ClipboardBackendCapabilities {\n platform: \"darwin\" | \"win32\";\n /** v1 is text-only; the format catalog is a v2 extension point. */\n textOnly: true;\n /** Frozen platform-independent write bound (UTF-16 code units). */\n maxWriteUtf16: typeof CLIPBOARD_MAX_WRITE_UTF16;\n /** win32 = true (bounded open-retry discipline); darwin = false (AppKit serializes). */\n boundedOpenRetry: boolean;\n}\n\n/** Returns true when an unknown value is a complete clipboard backend capabilities DTO. */\nexport const isClipboardBackendCapabilities = (\n value: unknown\n): value is ClipboardBackendCapabilities => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n const record = value as Record<string, unknown>;\n return (\n (record.platform === \"darwin\" || record.platform === \"win32\") &&\n record.textOnly === true &&\n record.maxWriteUtf16 === CLIPBOARD_MAX_WRITE_UTF16 &&\n typeof record.boundedOpenRetry === \"boolean\"\n );\n};\n\n/** Frozen clipboard typed-error code set (add-ext-clipboard design reference section 3). */\nexport const CLIPBOARD_ERROR_CODES = {\n platformUnsupported: \"clipboard_platform_unsupported\",\n locked: \"clipboard_locked\",\n unavailable: \"clipboard_unavailable\",\n payloadTooLarge: \"clipboard_payload_too_large\",\n payloadInvalid: \"clipboard_payload_invalid\",\n} as const;\n\nexport type ClipboardErrorCode = (typeof CLIPBOARD_ERROR_CODES)[keyof typeof CLIPBOARD_ERROR_CODES];\n\nexport const isClipboardErrorCode = (value: string): value is ClipboardErrorCode =>\n Object.values(CLIPBOARD_ERROR_CODES).includes(value as ClipboardErrorCode);\n\n// ---------------------------------------------------------------------------\n// Opener extension shared schema (add-ext-opener design reference)\n// ---------------------------------------------------------------------------\n\n/** Frozen v1 scheme allowlist (lowercase canonical; matched case-insensitively). */\nexport const OPENER_ALLOWED_SCHEMES = [\"http\", \"https\", \"file\", \"mailto\"] as const;\n\nexport type OpenerAllowedScheme = (typeof OPENER_ALLOWED_SCHEMES)[number];\n\nexport const isOpenerAllowedScheme = (value: string): value is OpenerAllowedScheme =>\n (OPENER_ALLOWED_SCHEMES as readonly string[]).includes(value.toLowerCase()) === true;\n\n/** Shared opener backend capabilities DTO (add-ext-opener design reference). */\nexport interface OpenerBackendCapabilities {\n platform: \"darwin\" | \"win32\";\n allowedSchemes: typeof OPENER_ALLOWED_SCHEMES;\n supportsRevealInFolder: true;\n}\n\n/** Returns true when an unknown value is a complete opener backend capabilities DTO. */\nexport const isOpenerBackendCapabilities = (\n value: unknown\n): value is OpenerBackendCapabilities => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n const record = value as Record<string, unknown>;\n if (record.platform !== \"darwin\" && record.platform !== \"win32\") {\n return false;\n }\n if (\n !Array.isArray(record.allowedSchemes) ||\n record.allowedSchemes.length !== OPENER_ALLOWED_SCHEMES.length ||\n !(record.allowedSchemes as unknown[]).every((scheme, index) => scheme === OPENER_ALLOWED_SCHEMES[index])\n ) {\n return false;\n }\n return record.supportsRevealInFolder === true;\n};\n\n/** Frozen opener typed-error code set (add-ext-opener design reference section 3). */\nexport const OPENER_ERROR_CODES = {\n platformUnsupported: \"opener_platform_unsupported\",\n targetInvalid: \"opener_target_invalid\",\n schemeBlocked: \"opener_scheme_blocked\",\n failed: \"opener_failed\",\n} as const;\n\nexport type OpenerErrorCode = (typeof OPENER_ERROR_CODES)[keyof typeof OPENER_ERROR_CODES];\n\nexport const isOpenerErrorCode = (value: string): value is OpenerErrorCode =>\n Object.values(OPENER_ERROR_CODES).includes(value as OpenerErrorCode);\n\n// ---------------------------------------------------------------------------\n// Notification extension shared schema (add-ext-notification design reference)\n// ---------------------------------------------------------------------------\n\n/** Frozen notification payload bounds (UTF-16 code units; platform-independent common subset). */\nexport const NOTIFICATION_TITLE_LIMIT_UTF16 = 64;\nexport const NOTIFICATION_BODY_LIMIT_UTF16 = 256;\nexport const NOTIFICATION_SUBTITLE_LIMIT_UTF16 = 64;\n\nexport type NotificationAuthorizationStatus =\n | \"granted\"\n | \"denied\"\n | \"notDetermined\";\n\nexport type NotificationAuthorizationModel = \"user\" | \"always-granted\";\nexport type NotificationChannel = \"user-notification-center\" | \"tray-icon-info\";\n\n/** Shared notification backend capabilities DTO (add-ext-notification design reference section 3). */\nexport interface NotificationBackendCapabilities {\n platform: \"darwin\" | \"win32\";\n authorizationModel: NotificationAuthorizationModel;\n channel: NotificationChannel;\n titleLimitUtf16: typeof NOTIFICATION_TITLE_LIMIT_UTF16;\n bodyLimitUtf16: typeof NOTIFICATION_BODY_LIMIT_UTF16;\n subtitleLimitUtf16: typeof NOTIFICATION_SUBTITLE_LIMIT_UTF16;\n supportsSubtitle: boolean;\n}\n\n/** Returns true when an unknown value is a complete notification backend capabilities DTO. */\nexport const isNotificationBackendCapabilities = (\n value: unknown\n): value is NotificationBackendCapabilities => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n const record = value as Record<string, unknown>;\n return (\n (record.platform === \"darwin\" || record.platform === \"win32\") &&\n (record.authorizationModel === \"user\" || record.authorizationModel === \"always-granted\") &&\n (record.channel === \"user-notification-center\" || record.channel === \"tray-icon-info\") &&\n record.titleLimitUtf16 === NOTIFICATION_TITLE_LIMIT_UTF16 &&\n record.bodyLimitUtf16 === NOTIFICATION_BODY_LIMIT_UTF16 &&\n record.subtitleLimitUtf16 === NOTIFICATION_SUBTITLE_LIMIT_UTF16 &&\n typeof record.supportsSubtitle === \"boolean\"\n );\n};\n\n/** Frozen notification typed-error code set (add-ext-notification design reference section 3). */\nexport const NOTIFICATION_ERROR_CODES = {\n platformUnsupported: \"notification_platform_unsupported\",\n denied: \"notification_denied\",\n payloadInvalid: \"notification_payload_invalid\",\n trayAbsent: \"notification_tray_absent\",\n failed: \"notification_failed\",\n} as const;\n\nexport type NotificationErrorCode =\n (typeof NOTIFICATION_ERROR_CODES)[keyof typeof NOTIFICATION_ERROR_CODES];\n\nexport const isNotificationErrorCode = (value: string): value is NotificationErrorCode =>\n Object.values(NOTIFICATION_ERROR_CODES).includes(value as NotificationErrorCode);\n","// Orthogonal intents (2026-09-18; add-ext-notification batch C):\n// 1. Keep every notification option/wire-event/error type plus the payload\n// preflight matrix and the win32 subtitle join as pure, transport-free\n// data (design reference sections 1-3). The DTO schema and the frozen\n// error-code family are owned by the shared @opentray/spec schema and\n// re-exported here so consumers need one import surface.\n// 2. Never perform I/O here; the facade (index.ts) composes these pure\n// pieces with the tray extension transport.\n\nimport {\n NOTIFICATION_BODY_LIMIT_UTF16,\n NOTIFICATION_ERROR_CODES,\n NOTIFICATION_SUBTITLE_LIMIT_UTF16,\n NOTIFICATION_TITLE_LIMIT_UTF16,\n isNotificationBackendCapabilities,\n type NotificationAuthorizationStatus,\n type NotificationBackendCapabilities,\n type NotificationErrorCode,\n} from \"@opentray/spec\";\n\nexport type NotificationPlatform = \"darwin\" | \"win32\" | \"linux\";\n\nexport type NotificationNativePlatform = \"darwin\" | \"win32\";\n\n// Shared schema (add-ext-notification batch A): re-exported so the facade\n// public surface and the @opentray/spec truth cannot drift.\nexport type {\n NotificationAuthorizationModel,\n NotificationAuthorizationStatus,\n NotificationBackendCapabilities,\n NotificationChannel,\n NotificationErrorCode,\n} from \"@opentray/spec\";\nexport {\n NOTIFICATION_BODY_LIMIT_UTF16,\n NOTIFICATION_ERROR_CODES,\n NOTIFICATION_SUBTITLE_LIMIT_UTF16,\n NOTIFICATION_TITLE_LIMIT_UTF16,\n isNotificationBackendCapabilities,\n isNotificationErrorCode,\n} from \"@opentray/spec\";\n\n// ---------------------------------------------------------------------------\n// Options (design reference section 1; v1 has NO platform namespace)\n// ---------------------------------------------------------------------------\n\nexport interface NotifyOptions {\n /** Required, non-empty, at most 64 UTF-16 code units (platform-independent common contract). */\n title: string;\n /** At most 256 UTF-16 code units. */\n body?: string;\n /**\n * Distinct native field on darwin. win32 documented degradation: the facade\n * joins it into the body as a prefix (combined post-join limit 256 UTF-16\n * code units; never truncated). At most 64 UTF-16 code units.\n */\n subtitle?: string;\n /** Default `false` (platform default alert sound); `true` means no sound. */\n silent?: boolean;\n}\n\n/** Wire form of a notify payload after facade preflight (`silent` always explicit). */\nexport interface NotifyWireOptions {\n title: string;\n body?: string;\n subtitle?: string;\n silent: boolean;\n}\n\n/** Wire command surface of the notification extension (camelCase `ext-command`\n * payloads). `notify` carries its fields FLAT next to `type` — the exact shape\n * the native `NotificationCommand` serde tag and the broker bridge decode\n * (implementation review I3a P0: a nested `{options: {...}}` wrapper never\n * reached a decoder). */\nexport type NotificationCommand =\n | { type: \"getBackend\" }\n | ({ type: \"notify\" } & NotifyWireOptions)\n | { type: \"getAuthorizationStatus\" }\n | { type: \"requestAuthorization\" };\n\n// ---------------------------------------------------------------------------\n// Wire events (design reference section 4): the two authorization commands\n// settle darwin-side through DeferredOperation terminals whose result value\n// carries the same event shape the win32 Immediate path emits as an ext\n// event — the facade stays transport-agnostic across both.\n// ---------------------------------------------------------------------------\n\n/** Wire event that answers the `getBackend` command on the immediate path. */\nexport interface NotificationBackendEvent {\n type: \"backend\";\n backend: NotificationBackendCapabilities;\n}\n\n/** Authorization read result: the `getAuthorizationStatus` answer. */\nexport interface NotificationAuthorizationEvent {\n type: \"authorization\";\n status: NotificationAuthorizationStatus;\n}\n\n/** Authorization request decision: the `requestAuthorization` answer. */\nexport interface NotificationAuthorizationDecisionEvent {\n type: \"authorizationDecision\";\n granted: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Typed error family (design reference section 3; codes frozen in @opentray/spec)\n// ---------------------------------------------------------------------------\n\n/**\n * Facade-originated details payloads (broker-originated details pass through\n * unchanged: `notification_denied` carries `{status}`; `notification_failed`\n * carries `{reason}` or an OS error code; the win32 bridge's\n * `notification_tray_absent` carries its own scope payload).\n */\nexport type NotificationErrorDetails =\n | { kind: \"platform\"; platform: string }\n | { field: string; lengthUtf16?: number; limit?: number };\n\nexport interface NotificationErrorDescriptor {\n code: NotificationErrorCode;\n message: string;\n details?: NotificationErrorDetails;\n}\n\n/**\n * Typed rejection of the notification facade: facade preflight failures\n * (platform, payload bounds/shape) and normalized broker/native notification\n * rejections. Consumers match on `code`; the human `message` is not a\n * contract. The shared transport-close code is surfaced unchanged (the frozen\n * notification family has no transport alias; core never branches on\n * extension names).\n */\nexport class NotificationError extends Error {\n readonly code: NotificationErrorCode;\n readonly details?: unknown;\n\n constructor(\n code: NotificationErrorCode,\n message: string,\n options: { details?: unknown; cause?: unknown } = {}\n ) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.name = \"NotificationError\";\n this.code = code;\n if (options.details !== undefined) {\n this.details = options.details;\n }\n }\n}\n\nexport const notificationErrorFromDescriptor = (\n descriptor: NotificationErrorDescriptor,\n options: { cause?: unknown } = {}\n): NotificationError =>\n new NotificationError(descriptor.code, descriptor.message, {\n ...(descriptor.details === undefined ? {} : { details: descriptor.details }),\n ...(options.cause === undefined ? {} : { cause: options.cause }),\n });\n\nexport const platformUnsupported = (platform: string): NotificationErrorDescriptor => ({\n code: NOTIFICATION_ERROR_CODES.platformUnsupported,\n message: `native notifications are unsupported on ${platform} (Linux has no native implementation; add-ext-notification design reference section 0)`,\n details: { kind: \"platform\", platform },\n});\n\n// ---------------------------------------------------------------------------\n// Runtime shape guards (wire results and DTO)\n// ---------------------------------------------------------------------------\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst AUTHORIZATION_STATUSES: readonly string[] = [\"granted\", \"denied\", \"notDetermined\"];\n\nexport const isNotificationBackendEvent = (\n value: unknown\n): value is NotificationBackendEvent =>\n isRecord(value) &&\n value.type === \"backend\" &&\n isNotificationBackendCapabilities(value.backend);\n\nexport const isNotificationAuthorizationEvent = (\n value: unknown\n): value is NotificationAuthorizationEvent =>\n isRecord(value) &&\n value.type === \"authorization\" &&\n typeof value.status === \"string\" &&\n AUTHORIZATION_STATUSES.includes(value.status);\n\nexport const isNotificationAuthorizationDecisionEvent = (\n value: unknown\n): value is NotificationAuthorizationDecisionEvent =>\n isRecord(value) && value.type === \"authorizationDecision\" && typeof value.granted === \"boolean\";\n\n// ---------------------------------------------------------------------------\n// Preflight validation (design reference section 1, frozen bounds; UTF-16\n// code units counted by String.length; deterministic order: object shape ->\n// unknown fields -> title -> body -> subtitle -> silent -> win32 joined\n// body). Everything here fails before any state change or transport use, and\n// payloads are never silently truncated.\n// ---------------------------------------------------------------------------\n\nconst NOTIFY_FIELDS = [\"title\", \"body\", \"subtitle\", \"silent\"] as const;\n\nconst payloadInvalid = (\n reason: string,\n field?: string,\n lengthUtf16?: number,\n limit?: number\n): NotificationErrorDescriptor => ({\n code: NOTIFICATION_ERROR_CODES.payloadInvalid,\n message:\n field === undefined\n ? `invalid notify options: ${reason}`\n : `invalid notify options (${field}): ${reason}`,\n ...(field === undefined\n ? {}\n : {\n details: {\n field,\n ...(lengthUtf16 === undefined ? {} : { lengthUtf16 }),\n ...(limit === undefined ? {} : { limit }),\n },\n }),\n});\n\n/**\n * win32 documented degradation (design reference section 1): the subtitle\n * prefixes the body through a single em dash; a subtitle without a body —\n * or with an EMPTY-string body — is the subtitle alone (never a dangling\n * separator; implementation review I3a P1 parity with the crate's\n * `win32_joined_body`). The joined string is what the combined 256-unit\n * limit measures.\n */\nexport const joinWin32Body = (subtitle: string, body: string | undefined): string =>\n body === undefined || body === \"\" ? subtitle : `${subtitle}—${body}`;\n\nexport const validateNotifyOptions = (\n options: unknown,\n platform: NotificationNativePlatform\n): NotificationErrorDescriptor | null => {\n if (!isRecord(options)) {\n return payloadInvalid(\"options must be an object\");\n }\n for (const key of Object.keys(options)) {\n if (!(NOTIFY_FIELDS as readonly string[]).includes(key)) {\n // v1 has no platform namespace and no passthrough fields (design\n // reference section 1); an unknown field is never silently ignored.\n return payloadInvalid(\"unknown field\", key);\n }\n }\n if (typeof options.title !== \"string\") {\n return payloadInvalid(\"must be a string\", \"title\");\n }\n if (options.title.length === 0) {\n return payloadInvalid(\"must be non-empty\", \"title\", 0, NOTIFICATION_TITLE_LIMIT_UTF16);\n }\n if (options.title.length > NOTIFICATION_TITLE_LIMIT_UTF16) {\n return payloadInvalid(\n `exceeds the ${NOTIFICATION_TITLE_LIMIT_UTF16} UTF-16 code unit limit`,\n \"title\",\n options.title.length,\n NOTIFICATION_TITLE_LIMIT_UTF16\n );\n }\n if (options.body !== undefined) {\n if (typeof options.body !== \"string\") {\n return payloadInvalid(\"must be a string when present\", \"body\");\n }\n if (options.body.length > NOTIFICATION_BODY_LIMIT_UTF16) {\n return payloadInvalid(\n `exceeds the ${NOTIFICATION_BODY_LIMIT_UTF16} UTF-16 code unit limit`,\n \"body\",\n options.body.length,\n NOTIFICATION_BODY_LIMIT_UTF16\n );\n }\n }\n if (options.subtitle !== undefined) {\n if (typeof options.subtitle !== \"string\") {\n return payloadInvalid(\"must be a string when present\", \"subtitle\");\n }\n if (options.subtitle.length > NOTIFICATION_SUBTITLE_LIMIT_UTF16) {\n return payloadInvalid(\n `exceeds the ${NOTIFICATION_SUBTITLE_LIMIT_UTF16} UTF-16 code unit limit`,\n \"subtitle\",\n options.subtitle.length,\n NOTIFICATION_SUBTITLE_LIMIT_UTF16\n );\n }\n }\n if (options.silent !== undefined && typeof options.silent !== \"boolean\") {\n return payloadInvalid(\"must be a boolean when present\", \"silent\");\n }\n if (platform === \"win32\" && options.subtitle !== undefined) {\n // The degradation join is jointly validated against the post-join 256\n // limit — an overflow rejects typed rather than truncating.\n const joined = joinWin32Body(options.subtitle, options.body);\n if (joined.length > NOTIFICATION_BODY_LIMIT_UTF16) {\n return payloadInvalid(\n `the joined subtitle-prefixed body exceeds the ${NOTIFICATION_BODY_LIMIT_UTF16} UTF-16 code unit limit (win32 subtitle degradation)`,\n \"body\",\n joined.length,\n NOTIFICATION_BODY_LIMIT_UTF16\n );\n }\n }\n return null;\n};\n\n/** Apply the frozen wire projection: `silent` explicit; win32 joins the subtitle into `body` (no `subtitle` on the win32 wire). */\nexport const notifyCommandOptions = (\n options: NotifyOptions,\n platform: NotificationNativePlatform\n): NotifyWireOptions => {\n if (platform === \"win32\" && options.subtitle !== undefined) {\n const joined = joinWin32Body(options.subtitle, options.body);\n return {\n title: options.title,\n body: joined,\n silent: options.silent === true,\n };\n }\n return {\n title: options.title,\n ...(options.body !== undefined ? { body: options.body } : {}),\n ...(options.subtitle !== undefined ? { subtitle: options.subtitle } : {}),\n silent: options.silent === true,\n };\n};\n","// Orthogonal intents (2026-09-18; add-ext-notification batch C):\n// 1. Attach the notification capability through the normal tray extension\n// family (tray.extend, session-scoped, isomorphic to\n// attachDialog/attachSound/attachClipboard).\n// 2. Fail every preflight branch before any transport use: typed platform\n// rejection on Linux (zero broker frames), the frozen payload bounds\n// matrix including the win32 subtitle-join combined limit, and unknown\n// attach option fields (TypeError) — never a silent truncation and never\n// a silently ignored setting.\n// 3. Dispatch notify/getBackend on the V2 immediate path (design reference\n// sections 1-2: notify is non-modal resolve-on-acceptance) and settle the\n// two authorization commands transport-agnostically: darwin's\n// DeferredOperation terminal (O2 ruling — the 10s timeout fires\n// broker-side into typed notification_failed) or win32's Immediate event\n// answer, whichever the broker resolves the request with.\n\nimport type {\n ExtensionRequestResult,\n NativeExtensionArtifact,\n TrayExtensionContext,\n TrayHandle,\n} from \"opentray\";\nimport {\n BrokerServerError,\n ExtensionOperationError,\n NativeExtensionEmbeddedArtifactError,\n} from \"opentray\";\n\nimport { NOTIFICATION_NATIVE_ARTIFACT } from \"./native-artifact\";\nimport {\n NOTIFICATION_ERROR_CODES,\n NotificationError,\n isNotificationAuthorizationDecisionEvent,\n isNotificationAuthorizationEvent,\n isNotificationBackendEvent,\n isNotificationErrorCode,\n notificationErrorFromDescriptor,\n notifyCommandOptions,\n platformUnsupported,\n validateNotifyOptions,\n type NotificationAuthorizationStatus,\n type NotificationBackendCapabilities,\n type NotificationCommand,\n type NotificationNativePlatform,\n type NotificationPlatform,\n type NotifyOptions,\n} from \"./shared\";\n\nexport type * from \"./shared\";\nexport {\n NOTIFICATION_ERROR_CODES,\n NotificationError,\n isNotificationAuthorizationDecisionEvent,\n isNotificationAuthorizationEvent,\n isNotificationBackendEvent,\n isNotificationErrorCode,\n joinWin32Body,\n notificationErrorFromDescriptor,\n validateNotifyOptions,\n} from \"./shared\";\nexport { NOTIFICATION_NATIVE_ARTIFACT } from \"./native-artifact\";\n\nconst NOTIFICATION_EXTENSION_NAME = \"notification\";\n\nconst ATTACH_OPTION_KEYS: ReadonlySet<string> = new Set([\"mountId\", \"artifact\", \"platform\"]);\n\nexport interface NotificationExtensionOptions {\n mountId?: string;\n artifact?: NativeExtensionArtifact;\n /** Test/seam override for the host platform; Linux rejects typed before any dispatch. */\n platform?: NotificationPlatform;\n}\n\n/**\n * Public notification capability (design reference section 1): host-side OS\n * notifications plus the authorization surface. `notify` is\n * resolve-on-acceptance (the sound law): whether and when the user actually\n * sees the notification is not part of the acceptance semantics. On darwin\n * the authorization commands carry real UNUserNotificationCenter semantics;\n * on win32 they are documented always-granted degradations answered\n * Immediate — the facade accepts both settle shapes for either platform.\n */\nexport interface NotificationCapability {\n notify(options: NotifyOptions): Promise<void>;\n getAuthorizationStatus(): Promise<NotificationAuthorizationStatus>;\n /** darwin: real decision. win32: documented degradation, always `true`. */\n requestAuthorization(): Promise<boolean>;\n getBackend(): Promise<NotificationBackendCapabilities>;\n}\n\nexport const attachNotification = (\n tray: TrayHandle,\n options: NotificationExtensionOptions = {}\n): NotificationCapability => {\n const unknown = Object.keys(options).find((key) => !ATTACH_OPTION_KEYS.has(key));\n if (unknown !== undefined) {\n // v1 has exactly the family option surface; an unknown field is a\n // programming error, never a silently ignored setting (pre-transport).\n throw new TypeError(\n `attachNotification received an unknown option field: ${unknown} (v1 accepts mountId, artifact, platform)`\n );\n }\n return tray.extend(NotificationExt, options);\n};\n\nexport const NotificationExt = {\n name: NOTIFICATION_EXTENSION_NAME,\n artifact: NOTIFICATION_NATIVE_ARTIFACT,\n resolveMount(options: NotificationExtensionOptions | undefined) {\n return {\n ...(options?.mountId === undefined ? {} : { mountId: options.mountId }),\n ...(options?.artifact === undefined ? {} : { artifact: options.artifact }),\n };\n },\n extend(\n tray: TrayHandle,\n context: TrayExtensionContext,\n options: NotificationExtensionOptions | undefined\n ): NotificationCapability {\n return createNotificationCapability(\n context,\n (options?.platform ?? process.platform) as NotificationPlatform\n );\n },\n} satisfies import(\"opentray\").TrayExtension<NotificationCapability, NotificationExtensionOptions>;\n\nfunction createNotificationCapability(\n context: TrayExtensionContext,\n platform: NotificationPlatform\n): NotificationCapability {\n const nativePlatform: NotificationNativePlatform | undefined =\n platform === \"darwin\" || platform === \"win32\" ? platform : undefined;\n\n const requirePlatform = (): NotificationNativePlatform => {\n if (nativePlatform === undefined) {\n throw notificationErrorFromDescriptor(platformUnsupported(platform));\n }\n return nativePlatform;\n };\n\n const mapTransportError = (error: unknown): unknown => {\n if (error instanceof NotificationError) {\n return error;\n }\n if (\n error instanceof NativeExtensionEmbeddedArtifactError &&\n error.reason === \"target-unsupported\"\n ) {\n return new NotificationError(\n NOTIFICATION_ERROR_CODES.platformUnsupported,\n `native notification extension ships no library for this platform: ${error.message}`,\n { details: { kind: \"platform\", platform }, cause: error }\n );\n }\n if (\n (error instanceof ExtensionOperationError || error instanceof BrokerServerError) &&\n isNotificationErrorCode(error.code)\n ) {\n // Includes the broker-side 10s authorization timeout: an error terminal\n // arrives as notification_failed with details {reason:\n // \"authorization-timeout\"} and normalizes here with details intact.\n return new NotificationError(error.code, error.message, {\n ...(error.details === undefined ? {} : { details: error.details }),\n cause: error,\n });\n }\n // The shared transport-close code and unknown codes surface unchanged:\n // the frozen notification family has no transport alias and core never\n // branches on extension names (add-ext-notification design reference\n // section 3).\n return error;\n };\n\n const dispatch = async <TValue>(\n command: NotificationCommand\n ): Promise<ExtensionRequestResult<TValue>> => {\n try {\n return await context.request<TValue>(command);\n } catch (error: unknown) {\n throw mapTransportError(error);\n }\n };\n\n const dispatchImmediate = async (\n command: NotificationCommand\n ): Promise<Extract<ExtensionRequestResult<unknown>, { kind: \"immediate\" }>> => {\n const result = await dispatch<unknown>(command);\n if (result.kind !== \"immediate\") {\n throw new Error(\n `notification extension command ${command.type} settles on the immediate path (add-ext-notification design reference sections 1-2; only the authorization commands defer); received ${result.kind}`\n );\n }\n return result;\n };\n\n /**\n * Transport-agnostic authorization settle (design reference section 4):\n * darwin completes through the DeferredOperation terminal whose result\n * value carries the event shape; win32 answers Immediate with the same\n * event shape in the events array. Either resolution path is accepted for\n * either platform — the facade cannot and must not tell them apart.\n */\n const runAuthorizationCommand = async <TEvent>(\n command: NotificationCommand,\n guard: (value: unknown) => value is TEvent,\n label: string\n ): Promise<TEvent> => {\n const result = await dispatch<unknown>(command);\n if (result.kind === \"terminal\") {\n if (!guard(result.value)) {\n throw new Error(`${label} deferred terminal payload violated the wire contract`);\n }\n return result.value;\n }\n for (const event of result.events) {\n const data = (event as { data?: unknown } | null)?.data;\n if (guard(data)) {\n return data;\n }\n }\n throw new Error(\n `${label} settled without an authorization payload (wire contract violation; expected a deferred terminal or an immediate authorization event)`\n );\n };\n\n const backendContractError = (): Error =>\n new Error(\n \"notification extension answered getBackend without a backend capabilities payload (wire contract violation)\"\n );\n\n const expectBackendPlatform = (\n backend: NotificationBackendCapabilities,\n native: NotificationNativePlatform\n ): NotificationBackendCapabilities => {\n if (backend.platform !== native) {\n throw new Error(\n `notification backend reported platform ${backend.platform} on a ${native} host (wire contract violation)`\n );\n }\n return backend;\n };\n\n /** Frozen immutable snapshot: the record is deeply plain, so a shallow freeze is total. */\n const freezeBackend = (backend: NotificationBackendCapabilities): NotificationBackendCapabilities =>\n Object.freeze({ ...backend });\n\n let backendPromise: Promise<NotificationBackendCapabilities> | undefined;\n\n const ensureBackend = (\n native: NotificationNativePlatform\n ): Promise<NotificationBackendCapabilities> => {\n backendPromise ??= (async () => {\n const result = await dispatchImmediate({ type: \"getBackend\" });\n for (const event of result.events) {\n const data = (event as { data?: unknown } | null)?.data;\n if (isNotificationBackendEvent(data)) {\n return freezeBackend(expectBackendPlatform(data.backend, native));\n }\n }\n throw backendContractError();\n })().catch((error: unknown) => {\n // Allow a later retry after a failed snapshot (e.g. transport death);\n // a successful snapshot is immutable and shared by every dispatch.\n backendPromise = undefined;\n throw mapTransportError(error);\n });\n return backendPromise;\n };\n\n return {\n notify: async (options: NotifyOptions): Promise<void> => {\n const native = requirePlatform();\n const issue = validateNotifyOptions(options, native);\n if (issue !== null) {\n throw notificationErrorFromDescriptor(issue);\n }\n // Resolve-on-acceptance: the immediate result IS the acceptance; the\n // win32 broker-internal tray-notification bridge answers with the same\n // immediate shape (design reference section 2, O1 ruling B). The\n // fields travel FLAT next to `type` — the shape the native serde tag\n // and the bridge decode (implementation review I3a P0).\n await dispatchImmediate({\n type: \"notify\",\n ...notifyCommandOptions(options, native),\n });\n },\n getAuthorizationStatus: async (): Promise<NotificationAuthorizationStatus> => {\n requirePlatform();\n const event = await runAuthorizationCommand(\n { type: \"getAuthorizationStatus\" },\n isNotificationAuthorizationEvent,\n \"getAuthorizationStatus\"\n );\n return event.status;\n },\n requestAuthorization: async (): Promise<boolean> => {\n requirePlatform();\n const event = await runAuthorizationCommand(\n { type: \"requestAuthorization\" },\n isNotificationAuthorizationDecisionEvent,\n \"requestAuthorization\"\n );\n return event.granted;\n },\n getBackend: async (): Promise<NotificationBackendCapabilities> =>\n ensureBackend(requirePlatform()),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/native-artifact.ts","../../spec/src/index.ts","../src/shared.ts","../src/index.ts"],"sourcesContent":["// Orthogonal intents (2026-09-18; add-ext-notification batch C):\n// 1. Declare the official notification platform library catalog as\n// platform-neutral data shipped inside this facade package (design\n// reference section 3, the archived dialog/sound embedded packaging law).\n// 2. Keep the embedded identity chain (facade package.json + contract.json)\n// relative to this facade instead of consumer cwd.\n\nimport type { NativeExtensionEmbeddedArtifact } from \"opentray\";\n\nexport const NOTIFICATION_NATIVE_ARTIFACT = {\n kind: \"embedded\",\n packageJsonUrl: new URL(\"../package.json\", import.meta.url).href,\n contractManifestUrl: new URL(\"../contract.json\", import.meta.url).href,\n targets: {\n \"darwin-arm64\": {\n libraryPath: \"platforms/darwin-arm64/libopentray_ext_notification.dylib\",\n },\n \"darwin-x64\": {\n libraryPath: \"platforms/darwin-x64/libopentray_ext_notification.dylib\",\n },\n \"win32-arm64\": {\n libraryPath: \"platforms/win32-arm64/opentray_ext_notification.dll\",\n },\n \"win32-x64\": {\n libraryPath: \"platforms/win32-x64/opentray_ext_notification.dll\",\n },\n },\n} satisfies NativeExtensionEmbeddedArtifact;\n","export type SessionId = string;\nexport type RequestId = string;\nexport type AppId = string;\nexport type TrayId = string;\nexport type MenuItemId = number;\n\n// Multi-webview orchestration, message-channel, and canonical JSON protocol\n// contracts (add-webview-orchestration); re-exported flat to keep one public\n// entry surface for consumers.\nexport * from \"./canonical-json\";\nexport * from \"./channel\";\nexport * from \"./webview\";\n\n/**\n * Protocol 2 (add-ext-dialog section 5.1) adds the DeferredOperation transaction\n * frames `ext-command-accepted` and `ext-operation-terminal`. The bump is one\n * matrix: Node client, broker, socket endpoint, and ready metadata all carry 2,\n * and a version-1 Init is rejected as incompatible (mirrors the Rust\n * `opentray-spec` PROTOCOL_VERSION truth).\n */\nexport const PROTOCOL_VERSION = 2;\nexport const OPENTRAY_PROTOCOL_FAMILY = \"opentray-protocol\";\n\n/**\n * Frozen wire form of a deferred operation id: exactly 16 lowercase hex\n * digits (the u64 handle's hex projection, add-ext-dialog design reference\n * section 5.7 ruling #2). Shared parser truth for `ext-command-accepted` and\n * `ext-operation-terminal`; empty, uppercase, non-hex, or wrong-length ids\n * are structurally invalid.\n */\nexport const OPERATION_ID_PATTERN = /^[0-9a-f]{16}$/u;\n\n/** Returns true when an unknown value matches the frozen 16-hex operation id form. */\nexport const isOperationId = (value: unknown): value is string =>\n typeof value === \"string\" && OPERATION_ID_PATTERN.test(value);\nexport const OPENTRAY_PROTOCOL_LINE_MAJOR = 1;\nexport const OPENTRAY_PROTOCOL_LINE_MINOR = 1;\n\nexport const protocolLineReleaseChannels = [\"stable\", \"alpha\"] as const;\nexport type ProtocolLineReleaseChannel = (typeof protocolLineReleaseChannels)[number];\n\nexport interface OpenTrayProtocolLine {\n family: typeof OPENTRAY_PROTOCOL_FAMILY;\n major: number;\n minor: number;\n}\n\nexport interface ProtocolDistTagOptions {\n channel: ProtocolLineReleaseChannel;\n major?: number;\n minor?: number;\n}\n\nexport interface ProtocolDistTag {\n channel: ProtocolLineReleaseChannel;\n major: number;\n minor: number;\n}\n\nexport const OPENTRAY_PROTOCOL_LINE: OpenTrayProtocolLine = {\n family: OPENTRAY_PROTOCOL_FAMILY,\n major: OPENTRAY_PROTOCOL_LINE_MAJOR,\n minor: OPENTRAY_PROTOCOL_LINE_MINOR,\n};\n\n// Install-time protocol lines can advance by minor version while runtime authority stays numeric.\nexport const compareOpenTrayProtocolLine = (\n left: OpenTrayProtocolLine,\n right: OpenTrayProtocolLine,\n): number => {\n assertOpenTrayProtocolLine(left, \"left\");\n assertOpenTrayProtocolLine(right, \"right\");\n if (left.major !== right.major) {\n return left.major - right.major;\n }\n return left.minor - right.minor;\n};\n\nexport const isOpenTrayProtocolLineCompatible = (\n supported: OpenTrayProtocolLine,\n required: OpenTrayProtocolLine,\n): boolean => {\n assertOpenTrayProtocolLine(supported, \"supported\");\n assertOpenTrayProtocolLine(required, \"required\");\n return supported.major === required.major && supported.minor >= required.minor;\n};\n\nexport const formatOpenTrayProtocolLine = ({\n family,\n major,\n minor,\n}: OpenTrayProtocolLine = OPENTRAY_PROTOCOL_LINE): string => {\n assertProtocolLineVersion(major, \"major\");\n assertProtocolLineVersion(minor, \"minor\");\n return `${family}/${major}.${minor}`;\n};\n\nexport const isProtocolLineReleaseChannel = (value: string): value is ProtocolLineReleaseChannel =>\n protocolLineReleaseChannels.includes(value as ProtocolLineReleaseChannel);\n\nexport const formatProtocolDistTag = ({\n channel,\n major = OPENTRAY_PROTOCOL_LINE_MAJOR,\n minor = OPENTRAY_PROTOCOL_LINE_MINOR,\n}: ProtocolDistTagOptions): string => {\n assertProtocolLineReleaseChannel(channel);\n assertProtocolLineVersion(major, \"major\");\n assertProtocolLineVersion(minor, \"minor\");\n return `${channel}-${major}-${minor}`;\n};\n\nexport const parseProtocolDistTag = (tag: string): ProtocolDistTag => {\n const match = /^(?<channel>[a-z]+)-(?<major>\\d+)-(?<minor>\\d+)$/u.exec(tag);\n const groups = match?.groups;\n if (groups === undefined) {\n throw new Error(`invalid OpenTray protocol dist-tag: ${tag}`);\n }\n const channel = groups.channel;\n if (channel === undefined || !isProtocolLineReleaseChannel(channel)) {\n throw new Error(`unsupported OpenTray protocol dist-tag channel: ${channel ?? \"\"}`);\n }\n const major = Number(groups.major);\n const minor = Number(groups.minor);\n assertProtocolLineVersion(major, \"major\");\n assertProtocolLineVersion(minor, \"minor\");\n return { channel, major, minor };\n};\n\n/**\n * Neutral caller label used when no usable caller identity can be derived.\n * Keeps the broker honest instead of impersonating an unrelated application.\n */\nexport const DEFAULT_CALLER_LABEL = \"opentray\";\n\n/**\n * Maximum length of a sanitized caller label. Keeps socket paths, runtime\n * directory names, and process titles within platform limits.\n */\nexport const CALLER_LABEL_MAX_LENGTH = 48;\n\nconst callerLabelAllowedPattern = /[a-z0-9-]+/g;\n\nexport interface BrokerEndpointIdentity {\n packageVersion: string;\n protocolVersion: number;\n callerLabel: string;\n}\n\nexport interface BrokerEndpointIdentityOptions {\n packageVersion: string;\n protocolVersion?: number;\n callerLabel?: string;\n}\n\nexport const sanitizeCallerLabel = (value: string | undefined): string => {\n if (value !== undefined && typeof value !== \"string\") {\n throw new Error(`callerLabel must be a string: ${String(value)}`);\n }\n const raw = value ?? \"\";\n const lowered = raw.toLowerCase();\n const segments = lowered.match(callerLabelAllowedPattern);\n if (segments === null || segments.length === 0) {\n return DEFAULT_CALLER_LABEL;\n }\n const joined = segments.join(\"-\");\n const trimmed = joined.replace(/^-+|-+$/gu, \"\");\n if (trimmed.length === 0) {\n return DEFAULT_CALLER_LABEL;\n }\n return trimmed.slice(0, CALLER_LABEL_MAX_LENGTH);\n};\n\nexport const createBrokerEndpointIdentity = ({\n packageVersion,\n protocolVersion = PROTOCOL_VERSION,\n callerLabel,\n}: BrokerEndpointIdentityOptions): BrokerEndpointIdentity => {\n assertEndpointComponent(packageVersion, \"packageVersion\");\n if (!Number.isInteger(protocolVersion) || protocolVersion <= 0) {\n throw new Error(`protocolVersion must be a positive integer: ${protocolVersion}`);\n }\n\n return {\n packageVersion,\n protocolVersion,\n callerLabel: sanitizeCallerLabel(callerLabel),\n };\n};\n\nexport const isSupportedProtocolVersion = (protocolVersion: number): boolean =>\n protocolVersion === PROTOCOL_VERSION;\n\nexport const formatBrokerEndpointName = (identity: BrokerEndpointIdentity): string => {\n assertEndpointIdentity(identity);\n return `opentray-${identity.packageVersion}-p${identity.protocolVersion}-${identity.callerLabel}`;\n};\n\nexport const formatBrokerStateRoot = (\n homeDir: string,\n identity: BrokerEndpointIdentity,\n): string => {\n assertEndpointIdentity(identity);\n if (homeDir.length === 0) {\n throw new Error(\"homeDir must not be empty\");\n }\n\n const normalizedHome = homeDir.replace(/[\\\\/]+$/u, \"\");\n return `${normalizedHome}/.opentray/${identity.packageVersion}/${identity.callerLabel}`;\n};\n\nexport const formatUnixSocketPath = (homeDir: string, identity: BrokerEndpointIdentity): string =>\n `${formatBrokerStateRoot(homeDir, identity)}/opentray-p${identity.protocolVersion}.sock`;\n\nexport const formatWindowsPipeName = (identity: BrokerEndpointIdentity): string =>\n `\\\\\\\\.\\\\pipe\\\\${formatBrokerEndpointName(identity)}`;\n\n/**\n * Human-readable process title for a broker pinned to a caller. Used by the SDK\n * spawn path so task managers show the owning application, not a generic name.\n */\nexport const formatBrokerProcessTitle = (identity: BrokerEndpointIdentity): string => {\n assertEndpointIdentity(identity);\n return `opentray · ${identity.callerLabel}`;\n};\n\nconst endpointComponentPattern = /^[0-9A-Za-z._+-]+$/u;\n\nconst assertOpenTrayProtocolLine = (line: OpenTrayProtocolLine, name: string): void => {\n assertProtocolLineVersion(line.major, `${name}.major`);\n assertProtocolLineVersion(line.minor, `${name}.minor`);\n};\n\nconst assertProtocolLineReleaseChannel = (channel: string): void => {\n if (!isProtocolLineReleaseChannel(channel)) {\n throw new Error(`unsupported OpenTray protocol release channel: ${channel}`);\n }\n};\n\nconst assertProtocolLineVersion = (value: number, name: string): void => {\n if (!Number.isInteger(value) || value < 0) {\n throw new Error(`protocol line ${name} must be a non-negative integer: ${value}`);\n }\n};\n\nconst assertEndpointIdentity = (identity: BrokerEndpointIdentity): void => {\n assertEndpointComponent(identity.packageVersion, \"packageVersion\");\n if (!Number.isInteger(identity.protocolVersion) || identity.protocolVersion <= 0) {\n throw new Error(`protocolVersion must be a positive integer: ${identity.protocolVersion}`);\n }\n};\n\nconst assertEndpointComponent = (value: string, name: string): void => {\n if (value.length === 0) {\n throw new Error(`${name} must not be empty`);\n }\n if (!endpointComponentPattern.test(value)) {\n throw new Error(`${name} contains invalid endpoint characters: ${value}`);\n }\n};\n\nexport interface AppOptions {\n id?: AppId;\n name?: string;\n appIcon?: AppIcon;\n default?: boolean;\n}\n\nexport interface AppRef {\n appId: AppId;\n}\n\nexport interface AppIdentity {\n appId: AppId;\n appName: string;\n appIcon?: AppIcon;\n appIconVariant?: string;\n}\n\nexport interface TrayOptions {\n id: string;\n tooltip?: Tooltip;\n icon?: Icon;\n menu?: Menu;\n}\n\nexport interface Tooltip {\n title: string;\n description: string;\n}\n\nexport interface Menu {\n items: MenuItem[];\n}\n\nexport type MenuItem =\n | {\n type: \"item\";\n id: MenuItemId;\n title: string;\n primaryEvent?: boolean;\n enabled?: boolean;\n shortcut?: string;\n }\n | {\n type: \"check\";\n id: MenuItemId;\n title: string;\n enabled?: boolean;\n checked?: boolean;\n }\n | {\n type: \"radio\";\n id: MenuItemId;\n title: string;\n enabled?: boolean;\n checked?: boolean;\n group: number;\n }\n | {\n type: \"separator\";\n }\n | {\n type: \"submenu\";\n title: string;\n enabled?: boolean;\n items: MenuItem[];\n };\n\nexport type IconImage =\n | { type: \"rgba\"; data: Uint8Array | number[]; width: number; height: number }\n | { type: \"encoded\"; data: Uint8Array | number[] }\n | { type: \"file\"; path: string };\n\n/** A source carrying one platform-native application icon file. */\nexport type AppIconSource =\n | { type: \"encoded\"; data: Uint8Array | readonly number[] }\n | { type: \"file\"; path: string };\n\n/** One or more application-defined semantic names for a native App icon asset. */\nexport type AppIconVariant = string | readonly string[];\n\ninterface AppIconVariantField {\n /** Omission is equivalent to the canonical `default` variant. */\n variant?: AppIconVariant;\n}\n\n/** One standards-compliant application icon asset for a specific operating system. */\nexport type AppIconAsset = AppIconVariantField &\n (\n | { platform: \"darwin\"; format: \"icns\"; source: AppIconSource }\n | { platform: \"windows\"; format: \"ico\"; source: AppIconSource }\n | {\n platform: \"linux\";\n format: \"png\";\n size: number;\n source: AppIconSource;\n }\n | { platform: \"linux\"; format: \"svg\"; source: AppIconSource }\n );\n\n/** Cross-platform application identity assets; tray `Icon` is a separate contract. */\nexport type AppIcon = readonly AppIconAsset[];\n\ntype AppIconAssetVariantName<TAsset> = TAsset extends {\n readonly variant: infer TVariant;\n}\n ? TVariant extends readonly string[]\n ? TVariant[number]\n : TVariant extends string\n ? TVariant\n : never\n : never;\n\n/** Literal variant names declared by an `as const` AppIcon catalog. */\nexport type AppIconVariantOf<TCatalog extends AppIcon> =\n | \"default\"\n | AppIconAssetVariantName<TCatalog[number]>;\n\nexport type SimpleIcon = IconImage & { text?: string };\n\nexport type IconText = IconImage & { text: string };\n\nexport type DarwinIcon = IconImage & { isTemplate?: boolean };\n\nexport type Win32Icon = IconImage;\n\nexport type LinuxIcon = IconImage;\n\nexport type DarwinIconText = DarwinIcon & { text: string };\n\nexport interface IconCandidates {\n \"icon-only\"?: IconImage;\n \"text-only\"?: string;\n \"icon-text\"?: IconText;\n \"darwin-icon-only\"?: DarwinIcon;\n \"darwin-icon-text\"?: DarwinIconText;\n \"win32-icon-only\"?: Win32Icon;\n \"win32-icon-text\"?: IconText;\n \"linux-icon-only\"?: LinuxIcon;\n \"linux-icon-text\"?: IconText;\n}\n\nexport type Icon = IconCandidates & Partial<SimpleIcon>;\n\nexport interface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport type TrayBoundsKind = \"native\" | \"inferred\" | \"unavailable\";\n\nexport interface TrayBoundsResult {\n kind: TrayBoundsKind;\n source: string;\n rect: Rect | null;\n}\n\nexport type MouseButton = \"left\" | \"right\" | \"middle\";\n\n/** Application-scoped lifecycle intent emitted by a native runtime carrier. */\nexport type AppEvent = { type: \"reopenRequested\"; appId: AppId };\n\nexport type TrayEvent =\n | { type: \"ready\"; appId: AppId }\n | { type: \"menuClick\"; appId: AppId; trayId: TrayId; itemId: MenuItemId }\n | {\n type: \"trayClick\";\n appId: AppId;\n trayId: TrayId;\n button: MouseButton;\n x: number;\n y: number;\n }\n | {\n type: \"trayDoubleClick\";\n appId: AppId;\n trayId: TrayId;\n button: MouseButton;\n x: number;\n y: number;\n };\n\nexport interface ExtensionScope {\n appId: AppId;\n trayId?: TrayId;\n ext: string;\n}\n\n/**\n * Broker-injected command ownership (add-ext-dialog section 5.5): the host derives\n * `{ appId, trayId, sessionId, instanceGeneration }` for every command\n * dispatch and extensions must never self-report it. All busy/operation\n * registries key on this scope; the deferred operation binding is\n * `(sessionId, instanceGeneration, operationId)`.\n */\nexport interface CommandScope {\n appId: AppId;\n trayId: TrayId;\n sessionId: SessionId;\n instanceGeneration: number;\n}\n\nexport interface ExtensionEnvelope<TData = unknown> {\n scope: ExtensionScope;\n /**\n * Present only on the command dispatch path: the broker-injected ownership\n * scope for this invocation. Event envelopes never carry it.\n */\n commandScope?: CommandScope;\n data: TData;\n}\n\n/**\n * Typed extension error envelope (add-ext-dialog section 7.5): `{ code, message,\n * details }` with a discriminated `details` JSON shape shared by the Rust\n * `ExtensionError::Detailed` projection, server error frames, and the Node\n * typed error factory. Consumers must match on `code`; parsing the human\n * `message` is forbidden by contract.\n */\nexport interface TypedExtensionError {\n code: string;\n message: string;\n /** Discriminated JSON payload whose shape each error code freezes. Absent for codes without structured detail. */\n details?: unknown;\n}\n\n/**\n * Terminal payload of a deferred operation (add-ext-dialog section 5.1 frozen): the\n * `result` branch resolves the client promise with `value`; the `error`\n * branch rejects it with a typed extension error. The cancel path is a\n * `result` payload isomorphic to user cancellation — there is no third\n * channel.\n */\nexport type ExtOperationPayload =\n | { kind: \"result\"; value: unknown }\n | { kind: \"error\"; error: TypedExtensionError };\n\n/**\n * Shared ingress bound for one extension event/terminal record (64 KiB). The\n * value is the single source of truth for both the EventPort record bound and\n * the DeferredPort terminal payload bound; the Rust `opentray-spec` crate\n * exports the same number (Rust/TS fixture parity).\n */\nexport const EXTENSION_EVENT_RECORD_MAX_BYTES = 64 * 1024;\n\nexport interface ExtensionArtifactTarget {\n os: string;\n arch: string;\n}\n\nexport interface ExpectedExtensionIdentity {\n extensionName: string;\n artifactSetVersion: string;\n contractFingerprint: string;\n target: ExtensionArtifactTarget;\n /**\n * Optional embedded-artifact identity-chain inputs (add-ext-dialog section 6.4):\n * lowercase hex SHA-256 of the resolved library file, verified by the broker\n * before `dlopen`. Absent means the caller supplied no byte hash\n * (registry-era identity only).\n */\n sha256?: string;\n /**\n * Optional build identity carried by the embedded staging manifest; the\n * broker compares it against the native manifest between `Library::new` and\n * `init` when provided.\n */\n buildIdentity?: string;\n}\n\n/** Operating-system and architecture identity for one broker executable. */\nexport interface BrokerArtifactTarget {\n os: \"darwin\" | \"linux\" | \"win32\";\n arch: \"arm64\" | \"x64\";\n}\n\n/** Content-derived identity used to decide whether a live broker may be reused. */\nexport interface BrokerArtifactIdentity {\n packageVersion: string;\n target: BrokerArtifactTarget;\n executableHash: string;\n buildIdentity: string;\n}\n\n/** Caller-scoped readiness evidence written by a successfully initialized broker. */\nexport interface BrokerReadyMetadata {\n pid: number;\n endpoint: string;\n packageVersion: string;\n protocolVersion: number;\n appId: AppId;\n appName: string;\n callerLabel: string;\n executablePath: string;\n brokerArtifactIdentity: BrokerArtifactIdentity;\n}\n\nexport interface RuntimeHostSessionHealth {\n sessionId: number;\n internalSessionId?: SessionId;\n initialized: boolean;\n}\n\nexport interface RuntimeHostHealth {\n pid: number;\n packageVersion: string;\n protocolVersion: number;\n endpoint: string;\n appId: AppId;\n appName: string;\n callerLabel: string;\n sessionCount: number;\n sessions: RuntimeHostSessionHealth[];\n}\n\nexport type ClientFrame =\n | { type: \"init\"; protocolVersion: number; clientVersion: string }\n | ClientRequestFrame\n | { type: \"exit\" };\n\nexport type ClientRequestFrame =\n | ({ type: \"create-app\"; requestId: RequestId } & AppOptions)\n | { type: \"resolve-default-app\"; requestId: RequestId }\n | { type: \"get-app-identity\"; requestId: RequestId; appId: AppId }\n | {\n type: \"set-app-name\";\n requestId: RequestId;\n appId: AppId;\n name: string;\n }\n | {\n type: \"set-app-icon\";\n requestId: RequestId;\n appId: AppId;\n appIcon: AppIcon | null;\n }\n | {\n type: \"set-app-icon-variant\";\n requestId: RequestId;\n appId: AppId;\n variant: string;\n }\n | {\n type: \"create-tray\";\n requestId: RequestId;\n app: AppRef;\n tray: TrayOptions;\n }\n | { type: \"destroy-tray\"; requestId: RequestId; appId: AppId; trayId: TrayId }\n | {\n type: \"get-tray-bounds\";\n requestId: RequestId;\n appId: AppId;\n trayId: TrayId;\n }\n | {\n type: \"set-tray-menu\";\n requestId: RequestId;\n appId: AppId;\n trayId: TrayId;\n menu: Menu;\n }\n | {\n type: \"set-tray-icon\";\n requestId: RequestId;\n appId: AppId;\n trayId: TrayId;\n icon: Icon;\n }\n | {\n type: \"set-tray-tooltip\";\n requestId: RequestId;\n appId: AppId;\n trayId: TrayId;\n tooltip: Tooltip;\n }\n | {\n type: \"load-ext\";\n requestId: RequestId;\n appId: AppId;\n name: string;\n path: string;\n expectedIdentity: ExpectedExtensionIdentity;\n mountId?: string;\n }\n | {\n type: \"ext-command\";\n requestId: RequestId;\n appId: AppId;\n trayId: TrayId;\n ext: string;\n data: unknown;\n }\n | { type: \"unload-ext\"; requestId: RequestId; appId: AppId; name: string }\n | { type: \"health\"; requestId: RequestId };\n\nexport type ServerFrame =\n | {\n type: \"ready\";\n protocolVersion: number;\n brokerVersion: string;\n brokerArtifactIdentity: BrokerArtifactIdentity;\n sessionId: SessionId;\n }\n | { type: \"app-created\"; requestId: RequestId; app: AppRef }\n | { type: \"default-app\"; requestId: RequestId; app: AppRef }\n | {\n type: \"app-identity\";\n requestId: RequestId;\n identity: AppIdentity;\n }\n | { type: \"tray-created\"; requestId: RequestId; appId: AppId; trayId: TrayId }\n | {\n type: \"tray-bounds\";\n requestId: RequestId;\n appId: AppId;\n trayId: TrayId;\n bounds: TrayBoundsResult;\n }\n | { type: \"ack\"; requestId: RequestId }\n | {\n type: \"ext-command-result\";\n requestId: RequestId;\n events: ExtensionEnvelope[];\n }\n | {\n /**\n * DeferredOperation acceptance (section 5.3): the command did not complete\n * inside the dispatch; the client keeps the request pending until the\n * matching `ext-operation-terminal` (or a transport-close rejection).\n */\n type: \"ext-command-accepted\";\n requestId: RequestId;\n operationId: string;\n }\n | {\n /**\n * The single terminal frame of a deferred operation. Exactly one is\n * delivered per accepted operation; duplicates are dropped by the\n * broker's owner loop before any frame is written.\n */\n type: \"ext-operation-terminal\";\n operationId: string;\n payload: ExtOperationPayload;\n }\n | {\n type: \"runtime-host-health\";\n requestId: RequestId;\n health: RuntimeHostHealth;\n }\n | { type: \"event\"; event: TrayEvent }\n | { type: \"app-event\"; event: AppEvent }\n | {\n type: \"ext-event\";\n appId: AppId;\n trayId: TrayId;\n ext: string;\n data: unknown;\n }\n | {\n /**\n * Synchronous broker error frame. Carries the same typed error envelope\n * as a deferred terminal's `error` branch (add-ext-dialog design\n * reference section 7.5): when the error code freezes a structured\n * payload it rides `details` as a JSON object; the field is absent for\n * codes without structured detail (never `null`).\n */\n type: \"error\";\n requestId?: RequestId;\n code: string;\n message: string;\n details?: unknown;\n };\n\nexport interface ParseResult<T> {\n ok: boolean;\n frame?: T;\n error?: string;\n}\n\nexport const parseServerFrame = (line: string): ParseResult<ServerFrame> => {\n try {\n const value: unknown = JSON.parse(line);\n if (!isServerFrame(value)) {\n return { ok: false, error: \"invalid server frame\" };\n }\n return { ok: true, frame: value };\n } catch (error) {\n return {\n ok: false,\n error: error instanceof Error ? error.message : \"unknown parse error\",\n };\n }\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\n/** Returns true when an unknown value is a complete broker artifact identity. */\nexport const isBrokerArtifactIdentity = (value: unknown): value is BrokerArtifactIdentity =>\n isRecord(value) &&\n typeof value.packageVersion === \"string\" &&\n isRecord(value.target) &&\n (value.target.os === \"darwin\" || value.target.os === \"linux\" || value.target.os === \"win32\") &&\n (value.target.arch === \"arm64\" || value.target.arch === \"x64\") &&\n typeof value.executableHash === \"string\" &&\n /^[a-f0-9]{64}$/u.test(value.executableHash) &&\n typeof value.buildIdentity === \"string\" &&\n value.buildIdentity.length > 0;\n\n/** Compares every compatibility-bearing broker artifact identity field. */\nexport const brokerArtifactIdentityEquals = (\n left: BrokerArtifactIdentity,\n right: BrokerArtifactIdentity,\n): boolean =>\n left.packageVersion === right.packageVersion &&\n left.target.os === right.target.os &&\n left.target.arch === right.target.arch &&\n left.executableHash === right.executableHash &&\n left.buildIdentity === right.buildIdentity;\n\nexport const isServerFrame = (value: unknown): value is ServerFrame => {\n if (!isRecord(value) || typeof value.type !== \"string\") {\n return false;\n }\n\n switch (value.type) {\n case \"ready\":\n return (\n typeof value.protocolVersion === \"number\" &&\n typeof value.brokerVersion === \"string\" &&\n isBrokerArtifactIdentity(value.brokerArtifactIdentity) &&\n typeof value.sessionId === \"string\"\n );\n case \"app-created\":\n return typeof value.requestId === \"string\" && isRecord(value.app);\n case \"default-app\":\n return typeof value.requestId === \"string\" && isRecord(value.app);\n case \"app-identity\":\n return typeof value.requestId === \"string\" && isRecord(value.identity);\n case \"tray-created\":\n return (\n typeof value.requestId === \"string\" &&\n typeof value.appId === \"string\" &&\n typeof value.trayId === \"string\"\n );\n case \"tray-bounds\":\n return (\n typeof value.requestId === \"string\" &&\n typeof value.appId === \"string\" &&\n typeof value.trayId === \"string\" &&\n isTrayBoundsResult(value.bounds)\n );\n case \"ack\":\n return typeof value.requestId === \"string\";\n case \"ext-command-result\":\n return (\n typeof value.requestId === \"string\" &&\n Array.isArray(value.events) &&\n value.events.every(isExtensionEnvelope)\n );\n case \"ext-command-accepted\":\n return (\n typeof value.requestId === \"string\" && isOperationId(value.operationId)\n );\n case \"ext-operation-terminal\":\n return isOperationId(value.operationId) && isExtOperationPayload(value.payload);\n case \"runtime-host-health\":\n return typeof value.requestId === \"string\" && isRuntimeHostHealth(value.health);\n case \"event\":\n return isTrayEvent(value.event);\n case \"app-event\":\n return isAppEvent(value.event);\n case \"ext-event\":\n return (\n typeof value.appId === \"string\" &&\n typeof value.trayId === \"string\" &&\n typeof value.ext === \"string\"\n );\n case \"error\":\n return (\n (value.requestId === undefined || typeof value.requestId === \"string\") &&\n typeof value.code === \"string\" &&\n typeof value.message === \"string\" &&\n // details is a JSON object when present (absent for codes without\n // structured detail); nulls and scalars are structurally invalid.\n (value.details === undefined || isRecord(value.details))\n );\n default:\n return false;\n }\n};\n\nconst isAppEvent = (value: unknown): value is AppEvent =>\n isRecord(value) &&\n value.type === \"reopenRequested\" &&\n typeof value.appId === \"string\";\n\nconst isExtensionEnvelope = (value: unknown): value is ExtensionEnvelope => {\n if (!isRecord(value) || !isRecord(value.scope)) {\n return false;\n }\n const scope = value.scope as Record<string, unknown>;\n return (\n typeof scope.appId === \"string\" &&\n (scope.trayId === undefined || typeof scope.trayId === \"string\") &&\n typeof scope.ext === \"string\" &&\n (value.commandScope === undefined || isCommandScope(value.commandScope)) &&\n \"data\" in value\n );\n};\n\n/** Returns true when an unknown value is a complete broker-injected command scope. */\nexport const isCommandScope = (value: unknown): value is CommandScope =>\n isRecord(value) &&\n typeof value.appId === \"string\" &&\n typeof value.trayId === \"string\" &&\n typeof value.sessionId === \"string\" &&\n typeof value.instanceGeneration === \"number\" &&\n Number.isInteger(value.instanceGeneration) &&\n value.instanceGeneration >= 0;\n\n/** Returns true when an unknown value is a complete typed extension error envelope. */\nexport const isTypedExtensionError = (value: unknown): value is TypedExtensionError =>\n isRecord(value) &&\n typeof value.code === \"string\" &&\n typeof value.message === \"string\" &&\n // details is a JSON object when present (absent for codes without\n // structured detail); nulls, scalars, and arrays are structurally invalid\n // on the synchronous error frame and on deferred terminal errors alike.\n (value.details === undefined || isRecord(value.details));\n\n/** Parser truth for the frozen terminal payload discriminant (no third branch is guessed). */\nexport const isExtOperationPayload = (value: unknown): value is ExtOperationPayload => {\n if (!isRecord(value)) {\n return false;\n }\n switch (value.kind) {\n case \"result\":\n return \"value\" in value;\n case \"error\":\n return isTypedExtensionError(value.error);\n default:\n return false;\n }\n};\n\nconst isRuntimeHostHealth = (value: unknown): value is RuntimeHostHealth => {\n if (!isRecord(value)) {\n return false;\n }\n\n return (\n typeof value.pid === \"number\" &&\n typeof value.packageVersion === \"string\" &&\n typeof value.protocolVersion === \"number\" &&\n typeof value.endpoint === \"string\" &&\n typeof value.appId === \"string\" &&\n typeof value.appName === \"string\" &&\n typeof value.callerLabel === \"string\" &&\n typeof value.sessionCount === \"number\" &&\n Array.isArray(value.sessions) &&\n value.sessions.every(isRuntimeHostSessionHealth)\n );\n};\n\nconst isRuntimeHostSessionHealth = (value: unknown): value is RuntimeHostSessionHealth =>\n isRecord(value) &&\n typeof value.sessionId === \"number\" &&\n (value.internalSessionId === undefined || typeof value.internalSessionId === \"string\") &&\n typeof value.initialized === \"boolean\";\n\nconst isRect = (value: unknown): value is Rect =>\n isRecord(value) &&\n typeof value.x === \"number\" &&\n typeof value.y === \"number\" &&\n typeof value.width === \"number\" &&\n typeof value.height === \"number\";\n\nconst isTrayBoundsResult = (value: unknown): value is TrayBoundsResult =>\n isRecord(value) &&\n (value.kind === \"native\" || value.kind === \"inferred\" || value.kind === \"unavailable\") &&\n typeof value.source === \"string\" &&\n (value.rect === null || isRect(value.rect));\n\nconst isTrayEvent = (value: unknown): value is TrayEvent => {\n if (!isRecord(value) || typeof value.type !== \"string\") {\n return false;\n }\n\n switch (value.type) {\n case \"ready\":\n return typeof value.appId === \"string\";\n case \"menuClick\":\n return (\n typeof value.appId === \"string\" &&\n typeof value.trayId === \"string\" &&\n typeof value.itemId === \"number\"\n );\n case \"trayClick\":\n case \"trayDoubleClick\":\n return (\n typeof value.appId === \"string\" &&\n typeof value.trayId === \"string\" &&\n isMouseButton(value.button) &&\n typeof value.x === \"number\" &&\n typeof value.y === \"number\"\n );\n default:\n return false;\n }\n};\n\nconst isMouseButton = (value: unknown): value is MouseButton =>\n value === \"left\" || value === \"right\" || value === \"middle\";\n\n// ---------------------------------------------------------------------------\n// Sound extension shared schema (add-ext-sound design reference sections 1-2,\n// task 2.1): the schema is promoted to this shared spec so `@opentray/ext-sound`\n// and the `opentray-spec` crate project the same DTO truth. The frozen\n// common-name table and the typed error family live here so facade, native,\n// and broker rejections stay code-identical across the wire.\n// ---------------------------------------------------------------------------\n\n/**\n * Alert-grade beep kinds (`beep` only, add-ext-sound design reference section\n * 1.1). Alert grading and named system sounds are two separate semantic\n * layers: `default`/`info`/`question` never enter the common system sound\n * catalog.\n */\nexport type BeepKind = \"default\" | \"info\" | \"warning\" | \"error\" | \"question\";\n\n/**\n * Common system sound names (add-ext-sound R1 section 4.1, frozen catalog:\n * no additions or removals in v1).\n */\nexport type CommonSystemSoundName = \"notification\" | \"warning\" | \"error\";\n\n/** Frozen common-name catalog in wire order; the facade consults it before any native passthrough. */\nexport const COMMON_SYSTEM_SOUND_NAMES: readonly CommonSystemSoundName[] = [\n \"notification\",\n \"warning\",\n \"error\",\n];\n\n/**\n * Frozen per-platform native projection of each common name (add-ext-sound\n * design reference section 1.2): darwin `NSSound` system catalog names and\n * win32 registry sound-scheme aliases (`SND_ALIAS`). Both records are frozen;\n * a changed projection is a contract change, not a data edit.\n */\nexport const COMMON_SYSTEM_SOUND_PROJECTIONS: Readonly<\n Record<CommonSystemSoundName, { readonly darwin: string; readonly win32: string }>\n> = Object.freeze({\n notification: Object.freeze({ darwin: \"Glass\", win32: \"SystemAsterisk\" }),\n warning: Object.freeze({ darwin: \"Sosumi\", win32: \"SystemExclamation\" }),\n error: Object.freeze({ darwin: \"Basso\", win32: \"SystemHand\" }),\n});\n\n/** Returns true when an unknown value is one of the frozen common system sound names. */\nexport const isCommonSystemSoundName = (\n value: string\n): value is CommonSystemSoundName =>\n (COMMON_SYSTEM_SOUND_NAMES as readonly string[]).includes(value);\n\n/**\n * A common name (typed union with autocomplete) or any platform-native sound\n * name (arbitrary string, resolved at runtime against the platform catalog).\n */\nexport type SystemSoundName = CommonSystemSoundName | (string & {});\n\n/**\n * v1 deliberately carries no options (add-ext-sound design reference section\n * 1.3 ruling: the rejection path already excluded volume control). Reserved\n * empty interface — future fields land in platform namespaces first (dialog\n * law), never as silently ignored values.\n */\nexport interface PlaySoundOptions {\n // Intentionally empty in v1.\n}\n\n/**\n * Shared sound backend capabilities DTO (add-ext-sound design reference\n * section 2). `fileFormats` is the v1 committed set (finite canonical:\n * darwin `[\"wav\",\"aiff\",\"mp3\",\"m4a\"]`, win32 `[\"wav\"]`), not a runtime-open\n * catalog of whatever the OS currently accepts.\n */\nexport interface SoundBackendCapabilities {\n platform: \"darwin\" | \"win32\";\n /** Platform-native sound-name catalog resolvable (`playSystemSound` passthrough). */\n systemSoundCatalog: boolean;\n /** File playback available (`playSound`). */\n playFile: boolean;\n fileFormats: readonly string[];\n}\n\n/** Returns true when an unknown value is a complete sound backend capabilities DTO. */\nexport const isSoundBackendCapabilities = (\n value: unknown\n): value is SoundBackendCapabilities => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n const record = value as Record<string, unknown>;\n if (record.platform !== \"darwin\" && record.platform !== \"win32\") {\n return false;\n }\n return (\n typeof record.systemSoundCatalog === \"boolean\" &&\n typeof record.playFile === \"boolean\" &&\n Array.isArray(record.fileFormats) &&\n record.fileFormats.every((format) => typeof format === \"string\")\n );\n};\n\n/** Typed sound error family (add-ext-sound design reference section 3, frozen at four codes). */\nexport const SOUND_ERROR_CODES = {\n platformUnsupported: \"sound_platform_unsupported\",\n notFound: \"sound_not_found\",\n formatUnsupported: \"sound_format_unsupported\",\n fileUnreadable: \"sound_file_unreadable\",\n} as const;\n\nexport type SoundErrorCode = (typeof SOUND_ERROR_CODES)[keyof typeof SOUND_ERROR_CODES];\n\nconst SOUND_ERROR_CODE_VALUES = new Set<string>(Object.values(SOUND_ERROR_CODES));\n\n/** Returns true when an unknown value is one of the frozen typed sound error codes. */\nexport const isSoundErrorCode = (value: string): value is SoundErrorCode =>\n SOUND_ERROR_CODE_VALUES.has(value);\n\n/** How a `sound_not_found` miss was searched, in resolution order (common table first). */\nexport type SystemSoundAttemptedMode = \"common-table\" | \"platform-catalog\";\n\n/**\n * Discriminated details payloads for the typed sound error family\n * (add-ext-sound design reference section 1.2: a miss never stays silent and\n * never falls back — `sound_not_found` details carry at least the requested\n * name, the platform, and the attempted modes/catalog).\n */\nexport type SoundErrorDetails =\n | { kind: \"platform\"; platform: string }\n | {\n kind: \"not-found\";\n requested: string;\n platform: \"darwin\" | \"win32\";\n attempted: readonly SystemSoundAttemptedMode[];\n }\n | { kind: \"format\"; path: string; reason: string }\n | { kind: \"unreadable\"; path: string };\n\n// ---------------------------------------------------------------------------\n// Clipboard extension shared schema (add-ext-clipboard design reference)\n// ---------------------------------------------------------------------------\n\n/** Frozen clipboard write bound: 1 MiB = 1,048,576 UTF-16 code units. */\nexport const CLIPBOARD_MAX_WRITE_UTF16 = 1_048_576;\n\n/** Shared clipboard backend capabilities DTO (add-ext-clipboard design reference). */\nexport interface ClipboardBackendCapabilities {\n platform: \"darwin\" | \"win32\";\n /** v1 is text-only; the format catalog is a v2 extension point. */\n textOnly: true;\n /** Frozen platform-independent write bound (UTF-16 code units). */\n maxWriteUtf16: typeof CLIPBOARD_MAX_WRITE_UTF16;\n /** win32 = true (bounded open-retry discipline); darwin = false (AppKit serializes). */\n boundedOpenRetry: boolean;\n}\n\n/** Returns true when an unknown value is a complete clipboard backend capabilities DTO. */\nexport const isClipboardBackendCapabilities = (\n value: unknown\n): value is ClipboardBackendCapabilities => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n const record = value as Record<string, unknown>;\n return (\n (record.platform === \"darwin\" || record.platform === \"win32\") &&\n record.textOnly === true &&\n record.maxWriteUtf16 === CLIPBOARD_MAX_WRITE_UTF16 &&\n typeof record.boundedOpenRetry === \"boolean\"\n );\n};\n\n/** Frozen clipboard typed-error code set (add-ext-clipboard design reference section 3). */\nexport const CLIPBOARD_ERROR_CODES = {\n platformUnsupported: \"clipboard_platform_unsupported\",\n locked: \"clipboard_locked\",\n unavailable: \"clipboard_unavailable\",\n payloadTooLarge: \"clipboard_payload_too_large\",\n payloadInvalid: \"clipboard_payload_invalid\",\n} as const;\n\nexport type ClipboardErrorCode = (typeof CLIPBOARD_ERROR_CODES)[keyof typeof CLIPBOARD_ERROR_CODES];\n\nexport const isClipboardErrorCode = (value: string): value is ClipboardErrorCode =>\n Object.values(CLIPBOARD_ERROR_CODES).includes(value as ClipboardErrorCode);\n\n// ---------------------------------------------------------------------------\n// Opener extension shared schema (add-ext-opener design reference)\n// ---------------------------------------------------------------------------\n\n/** Frozen v1 scheme allowlist (lowercase canonical; matched case-insensitively). */\nexport const OPENER_ALLOWED_SCHEMES = [\"http\", \"https\", \"file\", \"mailto\"] as const;\n\nexport type OpenerAllowedScheme = (typeof OPENER_ALLOWED_SCHEMES)[number];\n\nexport const isOpenerAllowedScheme = (value: string): value is OpenerAllowedScheme =>\n (OPENER_ALLOWED_SCHEMES as readonly string[]).includes(value.toLowerCase()) === true;\n\n/** Shared opener backend capabilities DTO (add-ext-opener design reference). */\nexport interface OpenerBackendCapabilities {\n platform: \"darwin\" | \"win32\";\n allowedSchemes: typeof OPENER_ALLOWED_SCHEMES;\n supportsRevealInFolder: true;\n}\n\n/** Returns true when an unknown value is a complete opener backend capabilities DTO. */\nexport const isOpenerBackendCapabilities = (\n value: unknown\n): value is OpenerBackendCapabilities => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n const record = value as Record<string, unknown>;\n if (record.platform !== \"darwin\" && record.platform !== \"win32\") {\n return false;\n }\n if (\n !Array.isArray(record.allowedSchemes) ||\n record.allowedSchemes.length !== OPENER_ALLOWED_SCHEMES.length ||\n !(record.allowedSchemes as unknown[]).every((scheme, index) => scheme === OPENER_ALLOWED_SCHEMES[index])\n ) {\n return false;\n }\n return record.supportsRevealInFolder === true;\n};\n\n/** Frozen opener typed-error code set (add-ext-opener design reference section 3). */\nexport const OPENER_ERROR_CODES = {\n platformUnsupported: \"opener_platform_unsupported\",\n targetInvalid: \"opener_target_invalid\",\n schemeBlocked: \"opener_scheme_blocked\",\n failed: \"opener_failed\",\n} as const;\n\nexport type OpenerErrorCode = (typeof OPENER_ERROR_CODES)[keyof typeof OPENER_ERROR_CODES];\n\nexport const isOpenerErrorCode = (value: string): value is OpenerErrorCode =>\n Object.values(OPENER_ERROR_CODES).includes(value as OpenerErrorCode);\n\n// ---------------------------------------------------------------------------\n// Notification extension shared schema (add-ext-notification design reference)\n// ---------------------------------------------------------------------------\n\n/** Frozen notification payload bounds (UTF-16 code units; platform-independent common subset). */\nexport const NOTIFICATION_TITLE_LIMIT_UTF16 = 64;\nexport const NOTIFICATION_BODY_LIMIT_UTF16 = 256;\nexport const NOTIFICATION_SUBTITLE_LIMIT_UTF16 = 64;\n\nexport type NotificationAuthorizationStatus =\n | \"granted\"\n | \"denied\"\n | \"notDetermined\";\n\nexport type NotificationAuthorizationModel = \"user\" | \"always-granted\";\nexport type NotificationChannel = \"user-notification-center\" | \"tray-icon-info\";\n\n/** Shared notification backend capabilities DTO (add-ext-notification design reference section 3). */\nexport interface NotificationBackendCapabilities {\n platform: \"darwin\" | \"win32\";\n authorizationModel: NotificationAuthorizationModel;\n channel: NotificationChannel;\n titleLimitUtf16: typeof NOTIFICATION_TITLE_LIMIT_UTF16;\n bodyLimitUtf16: typeof NOTIFICATION_BODY_LIMIT_UTF16;\n subtitleLimitUtf16: typeof NOTIFICATION_SUBTITLE_LIMIT_UTF16;\n supportsSubtitle: boolean;\n}\n\n/** Returns true when an unknown value is a complete notification backend capabilities DTO. */\nexport const isNotificationBackendCapabilities = (\n value: unknown\n): value is NotificationBackendCapabilities => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n const record = value as Record<string, unknown>;\n return (\n (record.platform === \"darwin\" || record.platform === \"win32\") &&\n (record.authorizationModel === \"user\" || record.authorizationModel === \"always-granted\") &&\n (record.channel === \"user-notification-center\" || record.channel === \"tray-icon-info\") &&\n record.titleLimitUtf16 === NOTIFICATION_TITLE_LIMIT_UTF16 &&\n record.bodyLimitUtf16 === NOTIFICATION_BODY_LIMIT_UTF16 &&\n record.subtitleLimitUtf16 === NOTIFICATION_SUBTITLE_LIMIT_UTF16 &&\n typeof record.supportsSubtitle === \"boolean\"\n );\n};\n\n/** Frozen notification typed-error code set (add-ext-notification design reference section 3). */\nexport const NOTIFICATION_ERROR_CODES = {\n platformUnsupported: \"notification_platform_unsupported\",\n denied: \"notification_denied\",\n payloadInvalid: \"notification_payload_invalid\",\n trayAbsent: \"notification_tray_absent\",\n failed: \"notification_failed\",\n} as const;\n\nexport type NotificationErrorCode =\n (typeof NOTIFICATION_ERROR_CODES)[keyof typeof NOTIFICATION_ERROR_CODES];\n\nexport const isNotificationErrorCode = (value: string): value is NotificationErrorCode =>\n Object.values(NOTIFICATION_ERROR_CODES).includes(value as NotificationErrorCode);\n","// Orthogonal intents (2026-09-18; add-ext-notification batch C):\n// 1. Keep every notification option/wire-event/error type plus the payload\n// preflight matrix and the win32 subtitle join as pure, transport-free\n// data (design reference sections 1-3). The DTO schema and the frozen\n// error-code family are owned by the shared @opentray/spec schema and\n// re-exported here so consumers need one import surface.\n// 2. Never perform I/O here; the facade (index.ts) composes these pure\n// pieces with the tray extension transport.\n\nimport {\n NOTIFICATION_BODY_LIMIT_UTF16,\n NOTIFICATION_ERROR_CODES,\n NOTIFICATION_SUBTITLE_LIMIT_UTF16,\n NOTIFICATION_TITLE_LIMIT_UTF16,\n isNotificationBackendCapabilities,\n type NotificationAuthorizationStatus,\n type NotificationBackendCapabilities,\n type NotificationErrorCode,\n} from \"@opentray/spec\";\n\nexport type NotificationPlatform = \"darwin\" | \"win32\" | \"linux\";\n\nexport type NotificationNativePlatform = \"darwin\" | \"win32\";\n\n// Shared schema (add-ext-notification batch A): re-exported so the facade\n// public surface and the @opentray/spec truth cannot drift.\nexport type {\n NotificationAuthorizationModel,\n NotificationAuthorizationStatus,\n NotificationBackendCapabilities,\n NotificationChannel,\n NotificationErrorCode,\n} from \"@opentray/spec\";\nexport {\n NOTIFICATION_BODY_LIMIT_UTF16,\n NOTIFICATION_ERROR_CODES,\n NOTIFICATION_SUBTITLE_LIMIT_UTF16,\n NOTIFICATION_TITLE_LIMIT_UTF16,\n isNotificationBackendCapabilities,\n isNotificationErrorCode,\n} from \"@opentray/spec\";\n\n// ---------------------------------------------------------------------------\n// Options (design reference section 1; v1 has NO platform namespace)\n// ---------------------------------------------------------------------------\n\nexport interface NotifyOptions {\n /** Required, non-empty, at most 64 UTF-16 code units (platform-independent common contract). */\n title: string;\n /** At most 256 UTF-16 code units. */\n body?: string;\n /**\n * Distinct native field on darwin. win32 documented degradation: the facade\n * joins it into the body as a prefix (combined post-join limit 256 UTF-16\n * code units; never truncated). At most 64 UTF-16 code units.\n */\n subtitle?: string;\n /** Default `false` (platform default alert sound); `true` means no sound. */\n silent?: boolean;\n}\n\n/** Wire form of a notify payload after facade preflight (`silent` always explicit). */\nexport interface NotifyWireOptions {\n title: string;\n body?: string;\n subtitle?: string;\n silent: boolean;\n}\n\n/** Wire command surface of the notification extension (camelCase `ext-command`\n * payloads). `notify` carries its fields FLAT next to `type` — the exact shape\n * the native `NotificationCommand` serde tag and the broker bridge decode\n * (implementation review I3a P0: a nested `{options: {...}}` wrapper never\n * reached a decoder). */\nexport type NotificationCommand =\n | { type: \"getBackend\" }\n | ({ type: \"notify\" } & NotifyWireOptions)\n | { type: \"getAuthorizationStatus\" }\n | { type: \"requestAuthorization\" };\n\n// ---------------------------------------------------------------------------\n// Wire events (design reference section 4): the two authorization commands\n// settle darwin-side through DeferredOperation terminals whose result value\n// carries the same event shape the win32 Immediate path emits as an ext\n// event — the facade stays transport-agnostic across both.\n// ---------------------------------------------------------------------------\n\n/** Wire event that answers the `getBackend` command on the immediate path. */\nexport interface NotificationBackendEvent {\n type: \"backend\";\n backend: NotificationBackendCapabilities;\n}\n\n/** Authorization read result: the `getAuthorizationStatus` answer. */\nexport interface NotificationAuthorizationEvent {\n type: \"authorization\";\n status: NotificationAuthorizationStatus;\n}\n\n/** Authorization request decision: the `requestAuthorization` answer. */\nexport interface NotificationAuthorizationDecisionEvent {\n type: \"authorizationDecision\";\n granted: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Typed error family (design reference section 3; codes frozen in @opentray/spec)\n// ---------------------------------------------------------------------------\n\n/**\n * Facade-originated details payloads (broker-originated details pass through\n * unchanged: `notification_denied` carries `{status}`; `notification_failed`\n * carries `{reason}` or an OS error code; the win32 bridge's\n * `notification_tray_absent` carries its own scope payload).\n */\nexport type NotificationErrorDetails =\n | { kind: \"platform\"; platform: string }\n | { field: string; lengthUtf16?: number; limit?: number };\n\nexport interface NotificationErrorDescriptor {\n code: NotificationErrorCode;\n message: string;\n details?: NotificationErrorDetails;\n}\n\n/**\n * Typed rejection of the notification facade: facade preflight failures\n * (platform, payload bounds/shape) and normalized broker/native notification\n * rejections. Consumers match on `code`; the human `message` is not a\n * contract. The shared transport-close code is surfaced unchanged (the frozen\n * notification family has no transport alias; core never branches on\n * extension names).\n */\nexport class NotificationError extends Error {\n readonly code: NotificationErrorCode;\n readonly details?: unknown;\n\n constructor(\n code: NotificationErrorCode,\n message: string,\n options: { details?: unknown; cause?: unknown } = {}\n ) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.name = \"NotificationError\";\n this.code = code;\n if (options.details !== undefined) {\n this.details = options.details;\n }\n }\n}\n\nexport const notificationErrorFromDescriptor = (\n descriptor: NotificationErrorDescriptor,\n options: { cause?: unknown } = {}\n): NotificationError =>\n new NotificationError(descriptor.code, descriptor.message, {\n ...(descriptor.details === undefined ? {} : { details: descriptor.details }),\n ...(options.cause === undefined ? {} : { cause: options.cause }),\n });\n\nexport const platformUnsupported = (platform: string): NotificationErrorDescriptor => ({\n code: NOTIFICATION_ERROR_CODES.platformUnsupported,\n message: `native notifications are unsupported on ${platform} (Linux has no native implementation; add-ext-notification design reference section 0)`,\n details: { kind: \"platform\", platform },\n});\n\n// ---------------------------------------------------------------------------\n// Runtime shape guards (wire results and DTO)\n// ---------------------------------------------------------------------------\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst AUTHORIZATION_STATUSES: readonly string[] = [\"granted\", \"denied\", \"notDetermined\"];\n\nexport const isNotificationBackendEvent = (\n value: unknown\n): value is NotificationBackendEvent =>\n isRecord(value) &&\n value.type === \"backend\" &&\n isNotificationBackendCapabilities(value.backend);\n\nexport const isNotificationAuthorizationEvent = (\n value: unknown\n): value is NotificationAuthorizationEvent =>\n isRecord(value) &&\n value.type === \"authorization\" &&\n typeof value.status === \"string\" &&\n AUTHORIZATION_STATUSES.includes(value.status);\n\nexport const isNotificationAuthorizationDecisionEvent = (\n value: unknown\n): value is NotificationAuthorizationDecisionEvent =>\n isRecord(value) && value.type === \"authorizationDecision\" && typeof value.granted === \"boolean\";\n\n// ---------------------------------------------------------------------------\n// Preflight validation (design reference section 1, frozen bounds; UTF-16\n// code units counted by String.length; deterministic order: object shape ->\n// unknown fields -> title -> body -> subtitle -> silent -> win32 joined\n// body). Everything here fails before any state change or transport use, and\n// payloads are never silently truncated.\n// ---------------------------------------------------------------------------\n\nconst NOTIFY_FIELDS = [\"title\", \"body\", \"subtitle\", \"silent\"] as const;\n\nconst payloadInvalid = (\n reason: string,\n field?: string,\n lengthUtf16?: number,\n limit?: number\n): NotificationErrorDescriptor => ({\n code: NOTIFICATION_ERROR_CODES.payloadInvalid,\n message:\n field === undefined\n ? `invalid notify options: ${reason}`\n : `invalid notify options (${field}): ${reason}`,\n ...(field === undefined\n ? {}\n : {\n details: {\n field,\n ...(lengthUtf16 === undefined ? {} : { lengthUtf16 }),\n ...(limit === undefined ? {} : { limit }),\n },\n }),\n});\n\n/**\n * win32 documented degradation (design reference section 1): the subtitle\n * prefixes the body through a single em dash; a subtitle without a body —\n * or with an EMPTY-string body — is the subtitle alone (never a dangling\n * separator; implementation review I3a P1 parity with the crate's\n * `win32_joined_body`). The joined string is what the combined 256-unit\n * limit measures.\n */\nexport const joinWin32Body = (subtitle: string, body: string | undefined): string =>\n body === undefined || body === \"\" ? subtitle : `${subtitle}—${body}`;\n\nexport const validateNotifyOptions = (\n options: unknown,\n platform: NotificationNativePlatform\n): NotificationErrorDescriptor | null => {\n if (!isRecord(options)) {\n return payloadInvalid(\"options must be an object\");\n }\n for (const key of Object.keys(options)) {\n if (!(NOTIFY_FIELDS as readonly string[]).includes(key)) {\n // v1 has no platform namespace and no passthrough fields (design\n // reference section 1); an unknown field is never silently ignored.\n return payloadInvalid(\"unknown field\", key);\n }\n }\n if (typeof options.title !== \"string\") {\n return payloadInvalid(\"must be a string\", \"title\");\n }\n if (options.title.length === 0) {\n return payloadInvalid(\"must be non-empty\", \"title\", 0, NOTIFICATION_TITLE_LIMIT_UTF16);\n }\n if (options.title.length > NOTIFICATION_TITLE_LIMIT_UTF16) {\n return payloadInvalid(\n `exceeds the ${NOTIFICATION_TITLE_LIMIT_UTF16} UTF-16 code unit limit`,\n \"title\",\n options.title.length,\n NOTIFICATION_TITLE_LIMIT_UTF16\n );\n }\n if (options.body !== undefined) {\n if (typeof options.body !== \"string\") {\n return payloadInvalid(\"must be a string when present\", \"body\");\n }\n if (options.body.length > NOTIFICATION_BODY_LIMIT_UTF16) {\n return payloadInvalid(\n `exceeds the ${NOTIFICATION_BODY_LIMIT_UTF16} UTF-16 code unit limit`,\n \"body\",\n options.body.length,\n NOTIFICATION_BODY_LIMIT_UTF16\n );\n }\n }\n if (options.subtitle !== undefined) {\n if (typeof options.subtitle !== \"string\") {\n return payloadInvalid(\"must be a string when present\", \"subtitle\");\n }\n if (options.subtitle.length > NOTIFICATION_SUBTITLE_LIMIT_UTF16) {\n return payloadInvalid(\n `exceeds the ${NOTIFICATION_SUBTITLE_LIMIT_UTF16} UTF-16 code unit limit`,\n \"subtitle\",\n options.subtitle.length,\n NOTIFICATION_SUBTITLE_LIMIT_UTF16\n );\n }\n }\n if (options.silent !== undefined && typeof options.silent !== \"boolean\") {\n return payloadInvalid(\"must be a boolean when present\", \"silent\");\n }\n if (platform === \"win32\" && options.subtitle !== undefined) {\n // The degradation join is jointly validated against the post-join 256\n // limit — an overflow rejects typed rather than truncating.\n const joined = joinWin32Body(options.subtitle, options.body);\n if (joined.length > NOTIFICATION_BODY_LIMIT_UTF16) {\n return payloadInvalid(\n `the joined subtitle-prefixed body exceeds the ${NOTIFICATION_BODY_LIMIT_UTF16} UTF-16 code unit limit (win32 subtitle degradation)`,\n \"body\",\n joined.length,\n NOTIFICATION_BODY_LIMIT_UTF16\n );\n }\n }\n return null;\n};\n\n/** Apply the frozen wire projection: `silent` explicit; win32 joins the subtitle into `body` (no `subtitle` on the win32 wire). */\nexport const notifyCommandOptions = (\n options: NotifyOptions,\n platform: NotificationNativePlatform\n): NotifyWireOptions => {\n if (platform === \"win32\" && options.subtitle !== undefined) {\n const joined = joinWin32Body(options.subtitle, options.body);\n return {\n title: options.title,\n body: joined,\n silent: options.silent === true,\n };\n }\n return {\n title: options.title,\n ...(options.body !== undefined ? { body: options.body } : {}),\n ...(options.subtitle !== undefined ? { subtitle: options.subtitle } : {}),\n silent: options.silent === true,\n };\n};\n","// Orthogonal intents (2026-09-18; add-ext-notification batch C):\n// 1. Attach the notification capability through the normal tray extension\n// family (tray.extend, session-scoped, isomorphic to\n// attachDialog/attachSound/attachClipboard).\n// 2. Fail every preflight branch before any transport use: typed platform\n// rejection on Linux (zero broker frames), the frozen payload bounds\n// matrix including the win32 subtitle-join combined limit, and unknown\n// attach option fields (TypeError) — never a silent truncation and never\n// a silently ignored setting.\n// 3. Dispatch notify/getBackend on the V2 immediate path (design reference\n// sections 1-2: notify is non-modal resolve-on-acceptance) and settle the\n// two authorization commands transport-agnostically: darwin's\n// DeferredOperation terminal (O2 ruling — the 10s timeout fires\n// broker-side into typed notification_failed) or win32's Immediate event\n// answer, whichever the broker resolves the request with.\n\nimport type {\n ExtensionRequestResult,\n NativeExtensionArtifact,\n TrayExtensionContext,\n TrayHandle,\n} from \"opentray\";\nimport {\n BrokerServerError,\n ExtensionOperationError,\n NativeExtensionEmbeddedArtifactError,\n} from \"opentray\";\n\nimport { NOTIFICATION_NATIVE_ARTIFACT } from \"./native-artifact\";\nimport {\n NOTIFICATION_ERROR_CODES,\n NotificationError,\n isNotificationAuthorizationDecisionEvent,\n isNotificationAuthorizationEvent,\n isNotificationBackendEvent,\n isNotificationErrorCode,\n notificationErrorFromDescriptor,\n notifyCommandOptions,\n platformUnsupported,\n validateNotifyOptions,\n type NotificationAuthorizationStatus,\n type NotificationBackendCapabilities,\n type NotificationCommand,\n type NotificationNativePlatform,\n type NotificationPlatform,\n type NotifyOptions,\n} from \"./shared\";\n\nexport type * from \"./shared\";\nexport {\n NOTIFICATION_ERROR_CODES,\n NotificationError,\n isNotificationAuthorizationDecisionEvent,\n isNotificationAuthorizationEvent,\n isNotificationBackendEvent,\n isNotificationErrorCode,\n joinWin32Body,\n notificationErrorFromDescriptor,\n validateNotifyOptions,\n} from \"./shared\";\nexport { NOTIFICATION_NATIVE_ARTIFACT } from \"./native-artifact\";\n\nconst NOTIFICATION_EXTENSION_NAME = \"notification\";\n\nconst ATTACH_OPTION_KEYS: ReadonlySet<string> = new Set([\"mountId\", \"artifact\", \"platform\"]);\n\nexport interface NotificationExtensionOptions {\n mountId?: string;\n artifact?: NativeExtensionArtifact;\n /** Test/seam override for the host platform; Linux rejects typed before any dispatch. */\n platform?: NotificationPlatform;\n}\n\n/**\n * Public notification capability (design reference section 1): host-side OS\n * notifications plus the authorization surface. `notify` is\n * resolve-on-acceptance (the sound law): whether and when the user actually\n * sees the notification is not part of the acceptance semantics. On darwin\n * the authorization commands carry real UNUserNotificationCenter semantics;\n * on win32 they are documented always-granted degradations answered\n * Immediate — the facade accepts both settle shapes for either platform.\n */\nexport interface NotificationCapability {\n notify(options: NotifyOptions): Promise<void>;\n getAuthorizationStatus(): Promise<NotificationAuthorizationStatus>;\n /** darwin: real decision. win32: documented degradation, always `true`. */\n requestAuthorization(): Promise<boolean>;\n getBackend(): Promise<NotificationBackendCapabilities>;\n}\n\nexport const attachNotification = (\n tray: TrayHandle,\n options: NotificationExtensionOptions = {}\n): NotificationCapability => {\n const unknown = Object.keys(options).find((key) => !ATTACH_OPTION_KEYS.has(key));\n if (unknown !== undefined) {\n // v1 has exactly the family option surface; an unknown field is a\n // programming error, never a silently ignored setting (pre-transport).\n throw new TypeError(\n `attachNotification received an unknown option field: ${unknown} (v1 accepts mountId, artifact, platform)`\n );\n }\n return tray.extend(NotificationExt, options);\n};\n\nexport const NotificationExt = {\n name: NOTIFICATION_EXTENSION_NAME,\n artifact: NOTIFICATION_NATIVE_ARTIFACT,\n resolveMount(options: NotificationExtensionOptions | undefined) {\n return {\n ...(options?.mountId === undefined ? {} : { mountId: options.mountId }),\n ...(options?.artifact === undefined ? {} : { artifact: options.artifact }),\n };\n },\n extend(\n tray: TrayHandle,\n context: TrayExtensionContext,\n options: NotificationExtensionOptions | undefined\n ): NotificationCapability {\n return createNotificationCapability(\n context,\n (options?.platform ?? process.platform) as NotificationPlatform\n );\n },\n} satisfies import(\"opentray\").TrayExtension<NotificationCapability, NotificationExtensionOptions>;\n\nfunction createNotificationCapability(\n context: TrayExtensionContext,\n platform: NotificationPlatform\n): NotificationCapability {\n const nativePlatform: NotificationNativePlatform | undefined =\n platform === \"darwin\" || platform === \"win32\" ? platform : undefined;\n\n const requirePlatform = (): NotificationNativePlatform => {\n if (nativePlatform === undefined) {\n throw notificationErrorFromDescriptor(platformUnsupported(platform));\n }\n return nativePlatform;\n };\n\n const mapTransportError = (error: unknown): unknown => {\n if (error instanceof NotificationError) {\n return error;\n }\n if (\n error instanceof NativeExtensionEmbeddedArtifactError &&\n error.reason === \"target-unsupported\"\n ) {\n return new NotificationError(\n NOTIFICATION_ERROR_CODES.platformUnsupported,\n `native notification extension ships no library for this platform: ${error.message}`,\n { details: { kind: \"platform\", platform }, cause: error }\n );\n }\n if (\n (error instanceof ExtensionOperationError || error instanceof BrokerServerError) &&\n isNotificationErrorCode(error.code)\n ) {\n // Includes the broker-side 10s authorization timeout: an error terminal\n // arrives as notification_failed with details {reason:\n // \"authorization-timeout\"} and normalizes here with details intact.\n return new NotificationError(error.code, error.message, {\n ...(error.details === undefined ? {} : { details: error.details }),\n cause: error,\n });\n }\n // The shared transport-close code and unknown codes surface unchanged:\n // the frozen notification family has no transport alias and core never\n // branches on extension names (add-ext-notification design reference\n // section 3).\n return error;\n };\n\n const dispatch = async <TValue>(\n command: NotificationCommand\n ): Promise<ExtensionRequestResult<TValue>> => {\n try {\n return await context.request<TValue>(command);\n } catch (error: unknown) {\n throw mapTransportError(error);\n }\n };\n\n const dispatchImmediate = async (\n command: NotificationCommand\n ): Promise<Extract<ExtensionRequestResult<unknown>, { kind: \"immediate\" }>> => {\n const result = await dispatch<unknown>(command);\n if (result.kind !== \"immediate\") {\n throw new Error(\n `notification extension command ${command.type} settles on the immediate path (add-ext-notification design reference sections 1-2; only the authorization commands defer); received ${result.kind}`\n );\n }\n return result;\n };\n\n /**\n * Transport-agnostic authorization settle (design reference section 4):\n * darwin completes through the DeferredOperation terminal whose result\n * value carries the event shape; win32 answers Immediate with the same\n * event shape in the events array. Either resolution path is accepted for\n * either platform — the facade cannot and must not tell them apart.\n */\n const runAuthorizationCommand = async <TEvent>(\n command: NotificationCommand,\n guard: (value: unknown) => value is TEvent,\n label: string\n ): Promise<TEvent> => {\n const result = await dispatch<unknown>(command);\n if (result.kind === \"terminal\") {\n if (!guard(result.value)) {\n throw new Error(`${label} deferred terminal payload violated the wire contract`);\n }\n return result.value;\n }\n for (const event of result.events) {\n const data = (event as { data?: unknown } | null)?.data;\n if (guard(data)) {\n return data;\n }\n }\n throw new Error(\n `${label} settled without an authorization payload (wire contract violation; expected a deferred terminal or an immediate authorization event)`\n );\n };\n\n const backendContractError = (): Error =>\n new Error(\n \"notification extension answered getBackend without a backend capabilities payload (wire contract violation)\"\n );\n\n const expectBackendPlatform = (\n backend: NotificationBackendCapabilities,\n native: NotificationNativePlatform\n ): NotificationBackendCapabilities => {\n if (backend.platform !== native) {\n throw new Error(\n `notification backend reported platform ${backend.platform} on a ${native} host (wire contract violation)`\n );\n }\n return backend;\n };\n\n /** Frozen immutable snapshot: the record is deeply plain, so a shallow freeze is total. */\n const freezeBackend = (backend: NotificationBackendCapabilities): NotificationBackendCapabilities =>\n Object.freeze({ ...backend });\n\n let backendPromise: Promise<NotificationBackendCapabilities> | undefined;\n\n const ensureBackend = (\n native: NotificationNativePlatform\n ): Promise<NotificationBackendCapabilities> => {\n backendPromise ??= (async () => {\n const result = await dispatchImmediate({ type: \"getBackend\" });\n for (const event of result.events) {\n const data = (event as { data?: unknown } | null)?.data;\n if (isNotificationBackendEvent(data)) {\n return freezeBackend(expectBackendPlatform(data.backend, native));\n }\n }\n throw backendContractError();\n })().catch((error: unknown) => {\n // Allow a later retry after a failed snapshot (e.g. transport death);\n // a successful snapshot is immutable and shared by every dispatch.\n backendPromise = undefined;\n throw mapTransportError(error);\n });\n return backendPromise;\n };\n\n return {\n notify: async (options: NotifyOptions): Promise<void> => {\n const native = requirePlatform();\n const issue = validateNotifyOptions(options, native);\n if (issue !== null) {\n throw notificationErrorFromDescriptor(issue);\n }\n // Resolve-on-acceptance over BOTH settle shapes (design reference\n // sections 1-2): win32's broker-internal tray bridge and darwin WITH\n // an authorization snapshot answer Immediate; darwin's FIRST\n // snapshot-less notify defers — the authorization query runs inside\n // the 10 s budget and the terminal resolves the acceptance. The\n // fields travel FLAT next to `type` — the shape the native serde tag\n // and the bridge decode (implementation review I3a P0). Found by the\n // host-atoms acceptance panel: the first real notify on a fresh\n // install settled deferred and this facade asserted Immediate only.\n const result = await dispatch<unknown>({\n type: \"notify\",\n ...notifyCommandOptions(options, native),\n });\n if (result.kind === \"immediate\") {\n return;\n }\n // Deferred acceptance: a happy terminal carries the notify result\n // event; error terminals surface through mapTransportError in\n // `dispatch` already (typed notification_failed etc.).\n },\n getAuthorizationStatus: async (): Promise<NotificationAuthorizationStatus> => {\n requirePlatform();\n const event = await runAuthorizationCommand(\n { type: \"getAuthorizationStatus\" },\n isNotificationAuthorizationEvent,\n \"getAuthorizationStatus\"\n );\n return event.status;\n },\n requestAuthorization: async (): Promise<boolean> => {\n requirePlatform();\n const event = await runAuthorizationCommand(\n { type: \"requestAuthorization\" },\n isNotificationAuthorizationDecisionEvent,\n \"requestAuthorization\"\n );\n return event.granted;\n },\n getBackend: async (): Promise<NotificationBackendCapabilities> =>\n ensureBackend(requirePlatform()),\n };\n}\n"],"mappings":";;AASA,MAAa,+BAA+B;CAC1C,MAAM;CACN,gBAAgB,IAAI,IAAI,mBAAmB,OAAO,KAAK,GAAG,CAAC,CAAC;CAC5D,qBAAqB,IAAI,IAAI,oBAAoB,OAAO,KAAK,GAAG,CAAC,CAAC;CAClE,SAAS;EACP,gBAAgB,EACd,aAAa,4DACf;EACA,cAAc,EACZ,aAAa,0DACf;EACA,eAAe,EACb,aAAa,sDACf;EACA,aAAa,EACX,aAAa,oDACf;CACF;AACF;AC49BI,OAAO,OAAO;CAChB,cAAc,OAAO,OAAO;EAAE,QAAQ;EAAS,OAAO;CAAiB,CAAC;CACxE,SAAS,OAAO,OAAO;EAAE,QAAQ;EAAU,OAAO;CAAoB,CAAC;CACvE,OAAO,OAAO,OAAO;EAAE,QAAQ;EAAS,OAAO;CAAa,CAAC;AAC/D,CAAC;AAoE+B,IAAI,IAAY,OAAO,OAAO;CAR5D,qBAAqB;CACrB,UAAU;CACV,mBAAmB;CACnB,gBAAgB;AAK4C,CAAiB,CAAC;;AA4JhF,MAAa,qCACX,UAC6C;CAC7C,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,GACpE,OAAO;CAET,MAAM,SAAS;CACf,QACG,OAAO,aAAa,YAAY,OAAO,aAAa,aACpD,OAAO,uBAAuB,UAAU,OAAO,uBAAuB,sBACtE,OAAO,YAAY,8BAA8B,OAAO,YAAY,qBACrE,OAAO,oBAAA,MACP,OAAO,mBAAA,OACP,OAAO,uBAAA,MACP,OAAO,OAAO,qBAAqB;AAEvC;;AAGA,MAAa,2BAA2B;CACtC,qBAAqB;CACrB,QAAQ;CACR,gBAAgB;CAChB,YAAY;CACZ,QAAQ;AACV;AAKA,MAAa,2BAA2B,UACtC,OAAO,OAAO,wBAAwB,CAAC,CAAC,SAAS,KAA8B;;;;;;;;;;;ACrnCjF,IAAa,oBAAb,cAAuC,MAAM;CAC3C;CACA;CAEA,YACE,MACA,SACA,UAAkD,CAAC,GACnD;EACA,MAAM,SAAS,QAAQ,UAAU,KAAA,IAAY,KAAA,IAAY,EAAE,OAAO,QAAQ,MAAM,CAAC;EACjF,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,IAAI,QAAQ,YAAY,KAAA,GACtB,KAAK,UAAU,QAAQ;CAE3B;AACF;AAEA,MAAa,mCACX,YACA,UAA+B,CAAC,MAEhC,IAAI,kBAAkB,WAAW,MAAM,WAAW,SAAS;CACzD,GAAI,WAAW,YAAY,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS,WAAW,QAAQ;CAC1E,GAAI,QAAQ,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO,QAAQ,MAAM;AAChE,CAAC;AAEH,MAAa,uBAAuB,cAAmD;CACrF,MAAM,yBAAyB;CAC/B,SAAS,2CAA2C,SAAS;CAC7D,SAAS;EAAE,MAAM;EAAY;CAAS;AACxC;AAMA,MAAM,YAAY,UAChB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAErE,MAAM,yBAA4C;CAAC;CAAW;CAAU;AAAe;AAEvF,MAAa,8BACX,UAEA,SAAS,KAAK,KACd,MAAM,SAAS,aACf,kCAAkC,MAAM,OAAO;AAEjD,MAAa,oCACX,UAEA,SAAS,KAAK,KACd,MAAM,SAAS,mBACf,OAAO,MAAM,WAAW,YACxB,uBAAuB,SAAS,MAAM,MAAM;AAE9C,MAAa,4CACX,UAEA,SAAS,KAAK,KAAK,MAAM,SAAS,2BAA2B,OAAO,MAAM,YAAY;AAUxF,MAAM,gBAAgB;CAAC;CAAS;CAAQ;CAAY;AAAQ;AAE5D,MAAM,kBACJ,QACA,OACA,aACA,WACiC;CACjC,MAAM,yBAAyB;CAC/B,SACE,UAAU,KAAA,IACN,2BAA2B,WAC3B,2BAA2B,MAAM,KAAK;CAC5C,GAAI,UAAU,KAAA,IACV,CAAC,IACD,EACE,SAAS;EACP;EACA,GAAI,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY;EACnD,GAAI,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM;CACzC,EACF;AACN;;;;;;;;;AAUA,MAAa,iBAAiB,UAAkB,SAC9C,SAAS,KAAA,KAAa,SAAS,KAAK,WAAW,GAAG,SAAS,GAAG;AAEhE,MAAa,yBACX,SACA,aACuC;CACvC,IAAI,CAAC,SAAS,OAAO,GACnB,OAAO,eAAe,2BAA2B;CAEnD,KAAK,MAAM,OAAO,OAAO,KAAK,OAAO,GACnC,IAAI,CAAE,cAAoC,SAAS,GAAG,GAGpD,OAAO,eAAe,iBAAiB,GAAG;CAG9C,IAAI,OAAO,QAAQ,UAAU,UAC3B,OAAO,eAAe,oBAAoB,OAAO;CAEnD,IAAI,QAAQ,MAAM,WAAW,GAC3B,OAAO,eAAe,qBAAqB,SAAS,GAAA,EAAiC;CAEvF,IAAI,QAAQ,MAAM,SAAA,IAChB,OAAO,eACL,yCACA,SACA,QAAQ,MAAM,QAAA,EAEhB;CAEF,IAAI,QAAQ,SAAS,KAAA,GAAW;EAC9B,IAAI,OAAO,QAAQ,SAAS,UAC1B,OAAO,eAAe,iCAAiC,MAAM;EAE/D,IAAI,QAAQ,KAAK,SAAA,KACf,OAAO,eACL,0CACA,QACA,QAAQ,KAAK,QAAA,GAEf;CAEJ;CACA,IAAI,QAAQ,aAAa,KAAA,GAAW;EAClC,IAAI,OAAO,QAAQ,aAAa,UAC9B,OAAO,eAAe,iCAAiC,UAAU;EAEnE,IAAI,QAAQ,SAAS,SAAA,IACnB,OAAO,eACL,yCACA,YACA,QAAQ,SAAS,QAAA,EAEnB;CAEJ;CACA,IAAI,QAAQ,WAAW,KAAA,KAAa,OAAO,QAAQ,WAAW,WAC5D,OAAO,eAAe,kCAAkC,QAAQ;CAElE,IAAI,aAAa,WAAW,QAAQ,aAAa,KAAA,GAAW;EAG1D,MAAM,SAAS,cAAc,QAAQ,UAAU,QAAQ,IAAI;EAC3D,IAAI,OAAO,SAAA,KACT,OAAO,eACL,yGACA,QACA,OAAO,QAAA,GAET;CAEJ;CACA,OAAO;AACT;;AAGA,MAAa,wBACX,SACA,aACsB;CACtB,IAAI,aAAa,WAAW,QAAQ,aAAa,KAAA,GAAW;EAC1D,MAAM,SAAS,cAAc,QAAQ,UAAU,QAAQ,IAAI;EAC3D,OAAO;GACL,OAAO,QAAQ;GACf,MAAM;GACN,QAAQ,QAAQ,WAAW;EAC7B;CACF;CACA,OAAO;EACL,OAAO,QAAQ;EACf,GAAI,QAAQ,SAAS,KAAA,IAAY,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC;EAC3D,GAAI,QAAQ,aAAa,KAAA,IAAY,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;EACvE,QAAQ,QAAQ,WAAW;CAC7B;AACF;;;AC5QA,MAAM,8BAA8B;AAEpC,MAAM,qCAA0C,IAAI,IAAI;CAAC;CAAW;CAAY;AAAU,CAAC;AA0B3F,MAAa,sBACX,MACA,UAAwC,CAAC,MACd;CAC3B,MAAM,UAAU,OAAO,KAAK,OAAO,CAAC,CAAC,MAAM,QAAQ,CAAC,mBAAmB,IAAI,GAAG,CAAC;CAC/E,IAAI,YAAY,KAAA,GAGd,MAAM,IAAI,UACR,wDAAwD,QAAQ,0CAClE;CAEF,OAAO,KAAK,OAAO,iBAAiB,OAAO;AAC7C;AAEA,MAAa,kBAAkB;CAC7B,MAAM;CACN,UAAU;CACV,aAAa,SAAmD;EAC9D,OAAO;GACL,GAAI,SAAS,YAAY,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS,QAAQ,QAAQ;GACrE,GAAI,SAAS,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU,QAAQ,SAAS;EAC1E;CACF;CACA,OACE,MACA,SACA,SACwB;EACxB,OAAO,6BACL,SACC,SAAS,YAAY,QAAQ,QAChC;CACF;AACF;AAEA,SAAS,6BACP,SACA,UACwB;CACxB,MAAM,iBACJ,aAAa,YAAY,aAAa,UAAU,WAAW,KAAA;CAE7D,MAAM,wBAAoD;EACxD,IAAI,mBAAmB,KAAA,GACrB,MAAM,gCAAgC,oBAAoB,QAAQ,CAAC;EAErE,OAAO;CACT;CAEA,MAAM,qBAAqB,UAA4B;EACrD,IAAI,iBAAiB,mBACnB,OAAO;EAET,IACE,iBAAiB,wCACjB,MAAM,WAAW,sBAEjB,OAAO,IAAI,kBACT,yBAAyB,qBACzB,qEAAqE,MAAM,WAC3E;GAAE,SAAS;IAAE,MAAM;IAAY;GAAS;GAAG,OAAO;EAAM,CAC1D;EAEF,KACG,iBAAiB,2BAA2B,iBAAiB,sBAC9D,wBAAwB,MAAM,IAAI,GAKlC,OAAO,IAAI,kBAAkB,MAAM,MAAM,MAAM,SAAS;GACtD,GAAI,MAAM,YAAY,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS,MAAM,QAAQ;GAChE,OAAO;EACT,CAAC;EAMH,OAAO;CACT;CAEA,MAAM,WAAW,OACf,YAC4C;EAC5C,IAAI;GACF,OAAO,MAAM,QAAQ,QAAgB,OAAO;EAC9C,SAAS,OAAgB;GACvB,MAAM,kBAAkB,KAAK;EAC/B;CACF;CAEA,MAAM,oBAAoB,OACxB,YAC6E;EAC7E,MAAM,SAAS,MAAM,SAAkB,OAAO;EAC9C,IAAI,OAAO,SAAS,aAClB,MAAM,IAAI,MACR,kCAAkC,QAAQ,KAAK,uIAAuI,OAAO,MAC/L;EAEF,OAAO;CACT;;;;;;;;CASA,MAAM,0BAA0B,OAC9B,SACA,OACA,UACoB;EACpB,MAAM,SAAS,MAAM,SAAkB,OAAO;EAC9C,IAAI,OAAO,SAAS,YAAY;GAC9B,IAAI,CAAC,MAAM,OAAO,KAAK,GACrB,MAAM,IAAI,MAAM,GAAG,MAAM,sDAAsD;GAEjF,OAAO,OAAO;EAChB;EACA,KAAK,MAAM,SAAS,OAAO,QAAQ;GACjC,MAAM,OAAQ,OAAqC;GACnD,IAAI,MAAM,IAAI,GACZ,OAAO;EAEX;EACA,MAAM,IAAI,MACR,GAAG,MAAM,sIACX;CACF;CAEA,MAAM,6CACJ,IAAI,MACF,6GACF;CAEF,MAAM,yBACJ,SACA,WACoC;EACpC,IAAI,QAAQ,aAAa,QACvB,MAAM,IAAI,MACR,0CAA0C,QAAQ,SAAS,QAAQ,OAAO,gCAC5E;EAEF,OAAO;CACT;;CAGA,MAAM,iBAAiB,YACrB,OAAO,OAAO,EAAE,GAAG,QAAQ,CAAC;CAE9B,IAAI;CAEJ,MAAM,iBACJ,WAC6C;EAC7C,oBAAoB,YAAY;GAC9B,MAAM,SAAS,MAAM,kBAAkB,EAAE,MAAM,aAAa,CAAC;GAC7D,KAAK,MAAM,SAAS,OAAO,QAAQ;IACjC,MAAM,OAAQ,OAAqC;IACnD,IAAI,2BAA2B,IAAI,GACjC,OAAO,cAAc,sBAAsB,KAAK,SAAS,MAAM,CAAC;GAEpE;GACA,MAAM,qBAAqB;EAC7B,EAAA,CAAG,CAAC,CAAC,OAAO,UAAmB;GAG7B,iBAAiB,KAAA;GACjB,MAAM,kBAAkB,KAAK;EAC/B,CAAC;EACD,OAAO;CACT;CAEA,OAAO;EACL,QAAQ,OAAO,YAA0C;GACvD,MAAM,SAAS,gBAAgB;GAC/B,MAAM,QAAQ,sBAAsB,SAAS,MAAM;GACnD,IAAI,UAAU,MACZ,MAAM,gCAAgC,KAAK;GAe7C,KAAI,MAJiB,SAAkB;IACrC,MAAM;IACN,GAAG,qBAAqB,SAAS,MAAM;GACzC,CAAC,EAAA,CACU,SAAS,aAClB;EAKJ;EACA,wBAAwB,YAAsD;GAC5E,gBAAgB;GAMhB,QAAO,MALa,wBAClB,EAAE,MAAM,yBAAyB,GACjC,kCACA,wBACF,EAAA,CACa;EACf;EACA,sBAAsB,YAA8B;GAClD,gBAAgB;GAMhB,QAAO,MALa,wBAClB,EAAE,MAAM,uBAAuB,GAC/B,0CACA,sBACF,EAAA,CACa;EACf;EACA,YAAY,YACV,cAAc,gBAAgB,CAAC;CACnC;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opentray/ext-notification",
|
|
3
|
-
"version": "0.30.
|
|
3
|
+
"version": "0.30.2",
|
|
4
4
|
"description": "Official OpenTray native notification extension: OS-standard host-side notifications through UNUserNotificationCenter or the registered tray icon's balloon/toast channel.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"tsdown": "^0.22.1",
|
|
30
30
|
"typescript": "^6.0.3",
|
|
31
31
|
"vitest": "^4.1.7",
|
|
32
|
-
"@opentray/spec": "0.30.
|
|
33
|
-
"opentray": "0.30.
|
|
32
|
+
"@opentray/spec": "0.30.2",
|
|
33
|
+
"opentray": "0.30.2"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "tsdown src/index.ts --format esm --dts",
|
|
Binary file
|
|
Binary file
|
package/platforms/manifest.json
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
"facadeVersion": "0.30.
|
|
2
|
+
"facadeVersion": "0.30.2",
|
|
3
3
|
"contractFingerprint": "opentray-ext-notification-contract-1",
|
|
4
4
|
"targets": {
|
|
5
5
|
"darwin-arm64": {
|
|
6
6
|
"path": "platforms/darwin-arm64/libopentray_ext_notification.dylib",
|
|
7
|
-
"sha256": "
|
|
8
|
-
"buildIdentity": "github:
|
|
7
|
+
"sha256": "ee786d24bee4309e32a5f7ec31c719dc5cabf1c294956a86a0b19986237c09f8",
|
|
8
|
+
"buildIdentity": "github:7b226d315f65acea9439af2167a583f0312ec0af"
|
|
9
9
|
},
|
|
10
10
|
"darwin-x64": {
|
|
11
11
|
"path": "platforms/darwin-x64/libopentray_ext_notification.dylib",
|
|
12
|
-
"sha256": "
|
|
13
|
-
"buildIdentity": "github:
|
|
12
|
+
"sha256": "d36462ea186463a0800b3fab334c47094614b358f08e5c2a92cf8e1119162a4a",
|
|
13
|
+
"buildIdentity": "github:7b226d315f65acea9439af2167a583f0312ec0af"
|
|
14
14
|
},
|
|
15
15
|
"win32-arm64": {
|
|
16
16
|
"path": "platforms/win32-arm64/opentray_ext_notification.dll",
|
|
17
|
-
"sha256": "
|
|
18
|
-
"buildIdentity": "github:
|
|
17
|
+
"sha256": "0c58e643bcf704a20ec986119f2b6e25a8fe81de0d3ab3a9cb655368959a54f9",
|
|
18
|
+
"buildIdentity": "github:7b226d315f65acea9439af2167a583f0312ec0af"
|
|
19
19
|
},
|
|
20
20
|
"win32-x64": {
|
|
21
21
|
"path": "platforms/win32-x64/opentray_ext_notification.dll",
|
|
22
|
-
"sha256": "
|
|
23
|
-
"buildIdentity": "github:
|
|
22
|
+
"sha256": "4ed5e0b59fe869d2fe0f51c79573b86f8bd8027b16dfdbe933d71e5c9577f0fe",
|
|
23
|
+
"buildIdentity": "github:7b226d315f65acea9439af2167a583f0312ec0af"
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|
|
Binary file
|
|
Binary file
|