/** * Generic seed context carried by an openable notification. The front-end trusts * these keys to reconstruct the tapped notification's chat, so they travel with * the payload rather than being re-derived downstream. * * `notificationId`, `subtype`, `createdAt`, and `contextText` are always present. * `address`/`asset`/`dex`/`traderAddress` are optional: runtime alerts are * strategy-scoped (no asset), discovery is trader-scoped (no strategy address), * so those fields may legitimately be absent. */ export interface NotificationContext { notificationId: string; subtype: string; createdAt: string; contextText: string; address?: string; asset?: string; dex?: string; traderAddress?: string; } /** * Push/in-app copy for a notification. * * There is no push title here: the notification service owns the push title and * sets it to the resolved strategy/skill name (looked up from * `actionMetadata.strategyWalletAddress`). The body therefore says only WHAT * happened and must not repeat the strategy name. */ export interface NotificationCopy { /** Push body, plain text (no markup); ≤128 after truncation. */ pushBody: string; /** In-app text, label-free (the front-end renders the label as its own tag); may carry literal `
` line breaks the front-end renders as newlines. */ inApp: string; } /** Hard limit the notification service enforces: an over-length body is dropped, not truncated server-side. */ export declare const PUSH_BODY_MAX = 128; /** Truncate to `max` UTF-16 units, appending "…" when clipped. Never splits a * surrogate pair: if the cut would land between an emoji's two units, it backs off * one so the result ends on a whole character rather than a lone `�`. */ export declare function truncateForPush(s: string, max: number): string; /** Fields common to every notification payload, openable or not. */ interface NotificationPayloadBase { message: string; recipientUserId?: string; subtype?: string; contextText?: string; /** Push/in-app copy block; when absent the envelope builder derives a fallback from `message`. */ copy?: NotificationCopy; /** * Wallet address of the strategy this notification is about. The notification * service resolves the displayed strategy/skill name from this field and * nowhere else, so it must be set whenever the address is known — regardless * of `canOpenAgentChat`. `context.address` is a different concern (the chat * seed key) and is dropped on the non-openable path; this one is not. * Omitted only when the address is genuinely unknown. */ strategyWalletAddress?: string; /** * Semantic (content-derived) dedupe keys. When set, they REPLACE the default * `actionMetadata.uniqueKeys = [notificationId]`, so the downstream ingest can * collapse near-duplicate alerts that describe the same real-world event (e.g. * two "Stop raised" for the same position/floor from two runtime processes). * `notificationId` stays a fresh UUID — it is the ledger row id and must remain * unique. Omitted by every family that wants only transport-retry dedupe. */ uniqueKeys?: string[]; /** * Semantic idempotency key. When set, it REPLACES the default * `idempotencyKey = notificationId` on the wire, so the ingest dedupes on the * content identity rather than the per-event row id. Set together with * {@link uniqueKeys} for the content-dedupe families; omitted otherwise. */ idempotencyKey?: string; } /** * Structured notification payload, discriminated on `canOpenAgentChat`. * * When `canOpenAgentChat` is `true` the generic seed `context` is REQUIRED — the * compiler catches an openable-without-context at the producer, before the flag * reaches a front-end that trusts it. When `false`, `context` is optional. * * Openability is opt-in and fail-safe: an absent flag is never treated as `true`. */ export type NotificationPayload = (NotificationPayloadBase & { canOpenAgentChat: true; context: NotificationContext; }) | (NotificationPayloadBase & { canOpenAgentChat: false; context?: NotificationContext; }); export interface NotificationService { notify(input: string | NotificationPayload): Promise; } /** * Normalize a notify() input to a structured payload. * * A bare string maps to a non-openable payload carrying that message. A structured * payload passes through (copied, never mutated). Openability is fail-safe: only * the literal boolean `true` enables the tap — absent, `undefined`, or any junk * value resolves to `false`. Never `Boolean(...)`-coerce, which would flip a * truthy non-boolean (or a truthy message) into openable. */ export declare function toPayload(input: string | NotificationPayload): NotificationPayload; export {}; //# sourceMappingURL=notification.d.ts.map