/** * `admin` namespace — operator-adjacent operations that don't fit a public * resource namespace cleanly: messages/contact plus internal finance reads. * * (The compound `init` and `status` flows live at the MCP/CLI edge because * they stitch together multiple SDK namespaces + local state.) */ import type { Client } from "../kernel.js"; import { Transfers } from "./transfers.js"; export interface AgentContact { name: string; email?: string; webhook?: string; } export type AgentEmailVerificationStatus = "none" | "pending" | "verified"; export type AgentPasskeyBindingStatus = "none" | "pending" | "verified"; export type AgentAssuranceLevel = "wallet_only" | "email_pending" | "email_verified" | "passkey_pending" | "operator_passkey"; export interface AgentContactResult { wallet: string; name: string; email: string | null; webhook: string | null; email_verification_status: AgentEmailVerificationStatus; passkey_binding_status: AgentPasskeyBindingStatus; assurance_level: AgentAssuranceLevel; email_verified_at: string | null; email_verified_message_id: string | null; email_challenge_sent_at: string | null; passkey_bound_at: string | null; active_operator_passkey_id: string | null; updated_at: string; verification_retry_after_seconds?: number; enrollment_sent_to?: string; } export interface SendMessageResult { status: string; } export interface FeedbackSendOptions { /** * Project this feedback concerns. Required to relay a promotion consent * (a `hand_to_operator` `next_actions` entry from a deploy response) — * the server resolves the project's site URL, org, and the sender's live * presence name for the delivered message. */ project_id?: string; /** Sender's X/Twitter handle, at most 64 characters. Delivered as-is; stored nowhere else. */ handle?: string; } export type AdminFinanceWindow = "24h" | "7d" | "30d" | "90d"; export interface AdminProjectFinanceOptions { /** Time window for the finance rollup. Defaults to "30d". */ window?: AdminFinanceWindow; /** * Optional admin session cookie header. Node operators can pass the value of * RUN402_ADMIN_COOKIE when they want browser-session auth; otherwise the * credential provider's normal auth headers are used. */ cookie?: string; } export interface AdminProjectFinanceResult { project_id: string; project_name: string; window: AdminFinanceWindow; revenue_usd_micros: number; direct_cost_usd_micros: number; direct_margin_usd_micros: number; revenue_breakdown: { tier_fees_usd_micros: number; email_packs_usd_micros: number; kms_rental_usd_micros: number; kms_sign_fees_usd_micros: number; per_call_sku_usd_micros: number; }; direct_cost_breakdown: Array<{ category: string; cost_usd_micros: number; }>; notes: string; } export type NotificationKind = "digest" | "lifecycle_event" | "threshold_alert" | "missing_verified_recipient"; export type NotificationChannel = "email" | "webhook" | "skipped"; export type NotificationDeliveryStatus = "delivered" | "failed_transient" | "failed_permanent" | "skipped_no_recipient" | "skipped_disabled"; export interface NotificationRow { id: string; recipient_email: string | null; kind: NotificationKind; event_type: string | null; channel: NotificationChannel; delivery_status: NotificationDeliveryStatus; delivery_error: string | null; attempt_count: number; is_test: boolean; related_project_id: string | null; related_organization_id: string | null; related_wallet_address: string | null; created_at: string; redacted_at: string | null; /** Payload JSON, or null when the row has been redacted. */ payload: Record | null; } export interface ListNotificationsOptions { type?: string; /** ISO timestamp; only notifications created at or after this time. */ since?: string; /** Default 50, max 200. */ limit?: number; /** Opaque keyset cursor — page forward from a prior page's `next_cursor`. */ after?: string; } export interface ListNotificationsResult { notifications: NotificationRow[]; has_more: boolean; next_cursor: string | null; } export interface NotificationPreferences { channels: { email: boolean; webhook: boolean; }; webhook_url: string | null; webhook_signing_secret_configured: boolean; digest_cadence: "off" | "daily" | "weekly" | "monthly"; /** 1=Monday..7=Sunday. */ digest_day_of_week: number; /** 0..23 UTC. */ digest_hour_utc: number; threshold_alerts: "off" | "digest_only" | "immediate"; lifecycle_events: "off" | "critical_only" | "all"; /** Schema-enforced; always "always". */ security_events: "always"; /** BCP-47 (e.g. "en-US"). */ locale: string; /** IANA timezone (e.g. "UTC"). */ timezone: string; } export type NotificationPreferencesPatch = Partial> & { /** Cannot be changed away from "always" — server returns 400. */ security_events?: "always"; }; export interface TestNotificationOptions { /** * Route the synthetic test event as if it came from the app lane * (`project_events.source = 'app'`) or the platform. Defaults to * `"platform"` on the gateway when omitted. */ source?: "app" | "platform"; /** * Synthetic `event_type` override (flat snake_case, * `^[a-z][a-z0-9_]{2,63}$`). Use this to exercise a specific routing rule's * `event_types` filter precisely. Defaults to the gateway's built-in * sample event when omitted. */ eventType?: string; } /** One Telegram destination's outcome from a `testNotification()` call — * present only when the operator has a routing rule matching the synthetic * event. Empty `telegram.destinations` is Faithful (no matching rule), not * an error. */ export interface TestNotificationDestination { binding_id: string; label: string | null; delivered: boolean; /** Present on failures. `true` = retryable (429/5xx/timeout/rate-limited); * `false` = permanent (bad chat, bot blocked/removed). */ transient?: boolean; description?: string; } export interface TestNotificationResult { status: "delivered" | "skipped" | "queued"; source_event_id: string; drained: { claimed: number; delivered: number; skipped: number; failed_transient: number; failed_permanent: number; }; /** Telegram delivery report for the synthetic event, routed through the * operator's normal rules — the full binding + rule + render + send * chain, not just email/webhook. */ telegram: { destinations: TestNotificationDestination[]; }; note: string; } export interface RotateWebhookSecretResult { webhook_signing_secret: string; rotated_at: string; grace_window_hours: number; note: string; } export type TelegramBindingStatus = "pending" | "active" | "revoked"; /** * One Telegram binding as returned by `GET /agent/v1/notifications/channels` * (`telegram[]`) and `r.admin.channels.list()`. Never carries the raw * connect code — codes are single-use, hashed at rest, and returned only * once, inline in {@link ConnectTelegramResult}. */ export interface TelegramChannelBinding { id: string; recipient_email: string; status: TelegramBindingStatus; chat_id: number | null; chat_type: string | null; chat_title: string | null; label: string | null; consecutive_failures: number; /** Set once auto-disabled after 10 consecutive hard delivery failures. */ disabled_at: string | null; /** Only set while `status === "pending"` — the connect code's 15-min TTL. */ code_expires_at: string | null; created_at: string; activated_at: string | null; } export interface ConnectTelegramOptions { /** Human-readable label for the chat (e.g. `"kychon alerts"`), 1-64 chars. */ label?: string; } /** * one-passkey-per-person: the two proofs a wallet-authenticated notification * mutation may carry on ONE request — the `SIGN-IN-WITH-X` wallet signature * (which contact) and the human's control-plane session bearer (the passkey * assurance). A person who logged in with `run402 operator login --loopback` * never enrolls a second passkey: the gateway accepts a passkey-fresh session * for the wallet contact's verified email as `operator_passkey` assurance. * When omitted, the request carries the provider's wallet auth alone — byte- * identical to before. */ export interface OperatorProofs { /** The `SIGN-IN-WITH-X` header value for the target path. */ siwx: string; /** The control-plane session token (`Authorization: Bearer`). */ token: string; } /** Request options carrying both proofs, or nothing when no session is at hand. */ export declare function operatorProofRequest(proofs?: OperatorProofs): { headers?: Record; withAuth?: boolean; }; export interface ConnectTelegramNextAction { type: string; method?: string; path?: string; why?: string; } export interface ConnectTelegramResult { binding_id: string; status: "pending"; /** `t.me/?start=` — tap to bind a PRIVATE chat. Single-use, 15-min TTL. */ connect_url: string; /** `t.me/?startgroup=` — tap to bind a GROUP chat. Same code/TTL as {@link connect_url} (whichever is tapped first consumes it). */ connect_group_url: string; code_expires_at: string; label: string | null; next_actions: ConnectTelegramNextAction[]; } export interface NotificationChannelsResult { email: { address: string | null; verified: boolean; }; webhook: { configured: boolean; url: string | null; secret_configured: boolean; }; /** Every live (non-revoked) Telegram binding for this operator, newest first. */ telegram: TelegramChannelBinding[]; } export interface RevokeTelegramResult { status: "revoked"; binding_id: string; } /** * `"app"` = app-emitted business events (`project_events.source = 'app'`, * e.g. `events.emit(...)` from `@run402/functions`); `"platform"` = every * non-app platform event (deploys, lifecycle, verification, ...). Absent / * `null` on a rule is a wildcard — matches both. */ export type RoutingRuleSource = "app" | "platform"; /** * Wire-shaped routing rule. Every match dimension (`project_id`, `source`, * `event_types`, `classes`) is ANDed; `null` is a wildcard for that * dimension. An explicit empty array (`event_types: []` / `classes: []`) * matches NOTHING — Postgres `TEXT[]` semantics, deliberately different from * the "`[]` means unfiltered" convention used by some read-filter query * params elsewhere in this SDK. One rule always targets exactly one Telegram * binding; overlapping rules that resolve to the same binding are deduped by * the gateway at delivery time (one message, not one per matching rule). */ export interface RoutingRule { id: string; recipient_email: string; project_id: string | null; source: RoutingRuleSource | null; event_types: string[] | null; classes: string[] | null; channel: "telegram"; telegram_binding_id: string; enabled: boolean; created_at: string; updated_at: string; } /** * `r.admin.rules.create(...)` input. Every match dimension is optional * (absent = wildcard); `telegramBindingId` is the only required field. An * all-wildcard rule (every event on every project routes to one chat) is * legal. */ export interface CreateRoutingRuleInput { telegramBindingId: string; projectId?: string | null; source?: RoutingRuleSource | null; eventTypes?: string[] | null; classes?: string[] | null; } /** * `r.admin.rules.update(...)` patch. PATCH semantics: a field OMITTED from * this object leaves the stored value unchanged; a field explicitly set to * `null` CLEARS that dimension back to wildcard. There is no wire difference * between "omitted" and "set to `undefined`" — both drop the key from the * JSON request body, so the gateway sees no instruction to change it. */ export interface UpdateRoutingRulePatch { projectId?: string | null; source?: RoutingRuleSource | null; eventTypes?: string[] | null; classes?: string[] | null; telegramBindingId?: string; enabled?: boolean; } export interface ListRoutingRulesResult { rules: RoutingRule[]; } export interface CreateRoutingRuleResult extends RoutingRule { next_actions: ConnectTelegramNextAction[]; } export interface DeleteRoutingRuleResult { deleted: true; rule_id: string; } /** * `r.admin.channels` — the Telegram notification-channel binding lifecycle * (connect / list / revoke). Mutations (`connectTelegram`, `revokeTelegram`) * require `operator_passkey` assurance; `connectTelegram` additionally * requires a VERIFIED operator email (bindings are addressed to it). See * `r.admin.setAgentContact` / `r.admin.verifyAgentContactEmail` and * `r.admin.startOperatorPasskeyEnrollment` to reach that assurance level — * same ladder as {@link Admin.rotateWebhookSecret}. */ export declare class Channels { private readonly client; constructor(client: Client); /** * Start binding a Telegram chat. Returns two single-use, 15-minute deep * links — `connect_url` for a private chat, `connect_group_url` for a * group — plus a `pending` binding id. A human taps ONE of the links and * starts the bot; poll {@link Channels.list} until the binding's `status` * flips to `"active"` (or `code_expires_at` passes and it's swept back to * `"revoked"`). * * Throws (via the generic SDK error hierarchy — check `err.code`) HTTP 503 * `TELEGRAM_CHANNEL_NOT_CONFIGURED` until the platform's dedicated * notification bot is provisioned, and HTTP 412 * `OPERATOR_EMAIL_NOT_VERIFIED` when the caller has no verified email yet. */ connectTelegram(opts?: ConnectTelegramOptions, proofs?: OperatorProofs): Promise; /** List every notification channel — email, webhook, and every live * (non-revoked) Telegram binding — for the authenticated wallet. */ list(): Promise; /** * Revoke a Telegram binding. Missing / already-revoked / another * operator's binding id all return the SAME not-found error * (authorize-before-reveal) — no existence oracle. */ revokeTelegram(bindingId: string, proofs?: OperatorProofs): Promise; } /** * `r.admin.rules` — Telegram routing rules: one match (ANDed * dimensions; an omitted dimension is a wildcard) → one Telegram binding. * Rules govern the Telegram channel ONLY in v1 — email/webhook keep their * existing preference-toggle semantics untouched. Mutations require * `operator_passkey` assurance. */ export declare class Rules { private readonly client; constructor(client: Client); /** List the operator's routing rules, newest first. */ list(): Promise; /** * Create a routing rule. `telegramBindingId` must reference a binding this * operator owns and that is currently usable (`status: "active"`); an * unusable or foreign binding id returns the same 404 as a nonexistent one * (authorize-before-reveal). */ create(input: CreateRoutingRuleInput, proofs?: OperatorProofs): Promise; /** * Patch a routing rule. PATCH semantics: only fields PRESENT on `patch` * are sent — `{ projectId: null }` clears that dimension back to * wildcard; omitting a field leaves it unchanged (see * {@link UpdateRoutingRulePatch}). */ update(ruleId: string, patch: UpdateRoutingRulePatch, proofs?: OperatorProofs): Promise; /** Delete a routing rule. */ delete(ruleId: string, proofs?: OperatorProofs): Promise; } export interface SetLeasePerpetualResult { status: "ok"; org_id: string; lease_perpetual: boolean; /** * `true` when the toggle was `lease_perpetual: true` AND the account was in * a grace state (past_due / frozen / dormant) and got pulled back to * `active` inline. `false` otherwise (account was already active, or the * toggle disabled perpetual). */ reactivated: boolean; } export interface ArchiveProjectOptions { /** Free-text moderation reason recorded in the audit log. */ reason?: string; } export interface ArchiveProjectResult { status: "ok"; project_id: string; /** ISO timestamp of the archive action. Absent when the project was already archived. */ archived_at?: string; /** Echoes the moderator-supplied reason when the project was newly archived. */ reason?: string; /** Set when the project was already archived; archived_at is then omitted. */ note?: "already archived"; } export interface ReactivateProjectResult { status: "ok"; project_id: string; /** `true` when the call un-archived a previously archived project. */ reactivated?: true; /** Set when the project was not archived to begin with — the call is a no-op. */ note?: "not archived"; } export interface OperatorStatusResult { operator_contact: { email_status: "none" | "pending" | "verified" | "bouncing"; passkey_status: "none" | "pending" | "verified"; recovery_gap: boolean; }; critical_items: Array<{ kind: string; detail: string; }>; skipped_notifications: Array<{ id: string; event_type: string; related_project_id: string | null; related_organization_id: string | null; related_wallet_address: string | null; created_at: string; }>; organizations: Array>; projects: Array>; active_thresholds: Array<{ resource: string; level: "warn" | "critical"; scope_kind: string; scope_id: string; crossed_at: string; last_observed_value: number | null; }>; /** * Runtime-staleness summary for the wallet's deployed functions * (capability `function-runtime-rebuild`, gateway v1.69+). A function is * stale when its deployed Lambda zip carries an older gateway entry wrapper * / bundled runtime than the gateway's current build. Read-only — observing * staleness never mutates a function. Refresh with `run402 functions * rebuild --all`. Omitted by gateways older than v1.69. */ runtime?: { stale_function_count: number; stale_functions: Array<{ project_id: string; name: string; }>; }; /** * Whether a mandatory-class (recovery/security/billing/verification) * notification can reach a verified human for the caller's org(s) — * computed from the union of the verified `agent_contacts` chain and * org-membership verified emails (recovery-event-reachability). When * `reachable` is false the response also carries a top-level * `next_actions[]` entry pointing at `POST /agent/v1/contact`. Omitted by * older gateways. */ operator_reachability?: { reachable: boolean; verified_recipient_count: number; sources: Array<"agent_contacts" | "org_membership">; /** Notifications skipped with no resolvable recipient, trailing 90 days. */ skipped_last_90d: number; }; /** Present when `operator_reachability.reachable` is false — the remedy. */ next_actions?: Array<{ type: string; method?: string; path?: string; why?: string; }>; } export declare class Admin { private readonly client; /** * Project transfer sub-namespace — unified wallet, email, and owned-org * project transfer surface. Access via `r.admin.transfers.{initiate, * preview, accept, claim, cancel, listIncoming, listOutgoing}`. */ readonly transfers: Transfers; /** * Telegram notification-channel binding lifecycle. Access via * `r.admin.channels.{connectTelegram, list, revokeTelegram}`. */ readonly channels: Channels; /** * Telegram routing rules — one match (ANDed dimensions) to one binding. * Access via `r.admin.rules.{list, create, update, delete}`. */ readonly rules: Rules; constructor(client: Client); /** * Operator-scoped sub-client for an org id — the operator analog of * `r.org(id)`, kept on `r.admin` because these actions require platform-admin * (`X-Admin-Mode`) auth, a different principal from the member-facing * `r.org(id)`. Exposes `pinLease()` / `unpinLease()`. Lazy and synchronous. */ org(orgId: string): ScopedAdminOrg; /** * Operator-scoped sub-client for a project id. Exposes `archive(opts?)`, * `reactivate()`, and `finance(opts?)` with the id pre-bound. Lazy and * synchronous. */ project(projectId: string): ScopedAdminProject; /** * Send feedback to the Run402 developers. Requires an active tier. * * WRITE-ONLY: there is no inbox and no reply path. When an answer from a * human is required, raise an escalation instead. * * Also the way a promotion consent is relayed: after a deploy response * carries a `hand_to_operator` next action, ask your human yes or no, and * on yes call `sendFeedback("promote: yes", { project_id, handle })`. */ sendFeedback(message: string, opts?: FeedbackSendOptions): Promise; /** * @deprecated Renamed to {@link sendFeedback}. Kept so code written against * the old name keeps compiling; it now posts to `/feedback/v1` like its * replacement. The `message` vocabulary is being freed for addressed * agent/human messaging, which is a different capability with a return path. */ sendMessage(message: string, opts?: FeedbackSendOptions): Promise; /** Register agent contact info and start email verification when needed. */ setAgentContact(contact: AgentContact): Promise; /** Read the current agent contact assurance state for the allowance wallet. */ getAgentContactStatus(): Promise; /** Start or resend the operator email reply challenge. */ verifyAgentContactEmail(): Promise; /** Email a passkey enrollment link to the verified operator email. */ startOperatorPasskeyEnrollment(): Promise; /** List operator notification audit rows (paginated, filterable). */ listNotifications(opts?: ListNotificationsOptions): Promise; /** Retrieve a single notification audit row by id. */ getNotification(id: string): Promise; /** Read the current operator notification preferences. */ getNotificationPreferences(): Promise; /** Patch operator notification preferences (assurance ladder applies). */ setNotificationPreferences(patch: NotificationPreferencesPatch, proofs?: OperatorProofs): Promise; /** * Trigger a real test notification. Sends a sample `project_past_due` * event through the normal worker pipeline (email/webhook); the audit row * is marked `is_test: true`. ALSO delivers a synthetic event through the * operator's Telegram routing rules end-to-end (binding + rule + render + * send) and reports a per-destination outcome in `telegram.destinations` * — pass `opts.source` / `opts.eventType` to target a specific rule's * filters instead of the default sample event. Rate-limited per wallet at * 1/min. */ testNotification(opts?: TestNotificationOptions): Promise; /** * Rotate the operator's webhook signing secret. The new plaintext secret * is returned EXACTLY once. The previous secret remains valid for 24 * hours (dual-secret grace window). Requires `operator_passkey` assurance. */ rotateWebhookSecret(proofs?: OperatorProofs): Promise; /** Compact operator-health snapshot for the authenticated wallet. */ getOperatorStatus(): Promise; /** * Fetch per-project finance for platform operators. * * This is the same admin-only surface used by the Run402 Finance tab. It is * gated by platform-admin auth; project service keys are not sufficient. * Use the Node SDK with an admin allowance wallet, pass `cookie` for browser * session auth, or provide a credential provider whose `getAuth()` returns * suitable admin headers. */ getProjectFinance(projectId: string, opts?: AdminProjectFinanceOptions): Promise; /** * Toggle a organization's `lease_perpetual` flag — the operator escape * hatch that pins every project in the organization (replaces the v1.56 * per-project `pin` removed in v1.57). When enabling on an organization in a * grace state, the gateway reactivates inline and reports it via * `reactivated: true`. * * Platform-admin only. Calls * `POST /orgs/v1/admin/:org_id/lease-perpetual`. */ /** * Shared, non-deprecated implementation behind {@link setLeasePerpetual} and * the `r.admin.org(id).pinLease()/unpinLease()` handle. * @internal */ _setLeasePerpetual(organizationId: string, perpetual: boolean): Promise; /** * Operator moderation action — archive a single project (ToS / abuse). * Sets `projects.archived_at` to NOW(). Independent of organization-level * lifecycle; the rest of the organization's projects continue serving. * * Platform-admin only. Calls `POST /projects/v1/admin/:id/archive`. */ archiveProject(projectId: string, opts?: ArchiveProjectOptions): Promise; /** * Operator "un-archive" — flips `projects.archived_at` back to NULL. It does * not touch organization-level lifecycle. To reactivate a grace-state * organization, either subscribe a new tier or set `lease_perpetual: true` * via {@link setLeasePerpetual}. * * Platform-admin only. Calls `POST /projects/v1/admin/:id/reactivate`. */ reactivateProject(projectId: string): Promise; } /** * Operator-scoped sub-client for a single org, returned by `r.admin.org(id)`. * Replaces the boolean `admin.setLeasePerpetual(orgId, perpetual)` with two * intent-named verbs. Carries platform-admin auth; operator-only errors surface * at call time. */ export declare class ScopedAdminOrg { private readonly admin; private readonly orgId; constructor(admin: Admin, orgId: string); /** Pin the org's lease (`lease_perpetual = true`). */ pinLease(): Promise; /** Unpin the org's lease (`lease_perpetual = false`). */ unpinLease(): Promise; } /** * Operator-scoped sub-client for a single project, returned by * `r.admin.project(id)`. The project id is pre-bound; methods carry * platform-admin auth. */ export declare class ScopedAdminProject { private readonly admin; private readonly projectId; constructor(admin: Admin, projectId: string); /** Archive the project (operator moderation). */ archive(opts?: ArchiveProjectOptions): Promise; /** Un-archive the project. */ reactivate(): Promise; /** Read per-project finance (operator Finance tab). */ finance(opts?: AdminProjectFinanceOptions): Promise; } //# sourceMappingURL=admin.d.ts.map