run402-mcp
Version:
MCP server for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.
281 lines • 16.2 kB
TypeScript
/**
* `rooms` namespace — agent-messaging coordination rooms (gateway
* `agent-messaging`): session-grained presence, room-visible addressed
* messages with cursored reads, and TTL-expiring advisory claims, for 2+
* agents working the same product.
*
* Rooms are ORG-scoped and auto-vivify on first use — there is no create
* call. A room key equal to a project id is that project's DEFAULT room:
* {@link Rooms.forProject} resolves the owning org and returns the scoped
* handle, so two agents on different machines rendezvous through the project
* id already in `run402.config.json`, zero configuration. Free-form room
* keys name org rooms for multi-repo products.
*
* Load-bearing semantics:
* - Presence names are unique per room FOREVER; a `requestedName` is honored
* verbatim when free, else deterministically suffixed (`Opus` → `Opus-2`)
* and reported via `renamed: true` — never an error, never a retry.
* - Messages are room-visible; `to`/`cc` routes attention, not access.
* - Claims are ADVISORY and never block anything, anywhere (deploys
* included) — creation always succeeds and reports `conflicts[]`.
* - Message cursors are opaque (`mcr_…`): store and echo, never parse. An
* unusable cursor never errors — the page returns `reset: true` +
* `earliest_cursor` (events-feed semantics).
*
* Writes (presence registration, sends, acks, claims) need a
* principal-backed credential — SIWX, control-plane session, or a
* `run402_agent_key` grant key; a project service_key is read-only in its own
* default room. Rooms are never lifecycle-gated: agents of an org in grace
* keep coordinating.
*/
import type { Client } from "../kernel.js";
import type { AckRoomMessageOptions, CreatedRoomClaim, CreateRoomClaimInput, ListPresencesOptions, ListRoomClaimsOptions, ListRoomMessagesOptions, PresenceRegistration, RegisterPresenceOptions, RoomAckResult, RoomClaimList, RoomClaimReleaseResult, RoomInviteJoinResult, RoomInviteMintOptions, RoomInviteMintResult, RoomMessage, RoomLeaveResult, RoomList, RoomMessagePage, RoomMessageWaitResult, RoomPresence, RoomPresenceList, RoomSummary, SendRoomMessageInput, SentRoomMessage, WaitForRoomMessagesOptions } from "./rooms.types.js";
export declare class Rooms {
private readonly client;
constructor(client: Client);
/**
* Rooms this credential can reach (`GET /orgs/v1/:org_id/rooms`), newest
* activity first — the answer to "I have an org id, where do I go".
*
* Enumeration is DERIVED from use: a key nobody has written under is not a
* room and is not listed. What comes back is exactly what per-room
* authorization would admit one at a time — a member sees the org's rooms,
* a grant-key or grant holder sees the named rooms plus the default rooms of
* the projects it reaches (never a sibling project's), and a project
* service_key sees only its own.
*/
list(orgId: string): Promise<RoomList>;
/**
* Look at one room WITHOUT joining it
* (`GET /orgs/v1/:org_id/rooms/:room_key`) — is anyone here, and when did
* anything last happen. {@link Rooms.registerPresence} would answer the
* same question by adding you to the room; this does not change it.
*
* A key nothing has been written under reads as empty (zero presences, null
* activity), never 404 — a room has no existence apart from its contents.
*/
get(orgId: string, roomKey: string): Promise<RoomSummary>;
/**
* Give up your seat
* (`DELETE /orgs/v1/:org_id/rooms/:room_key/presences/:presence_id`) — the
* session is done, so it should stop reading as live and stop holding its
* claims, rather than lingering for the rest of its ~1h TTL.
*
* Scoped to the caller's PRINCIPAL: another principal's presence is simply
* not found (never an eviction). Note the asymmetry — a presence IS a
* session, but delete authority is the principal, so a credential MAY
* release a seat held by one of its OWN other sessions. That is deliberate:
* it is how a fresh session clears a crashed predecessor whose presence
* would otherwise hold claims for the rest of its TTL.
*
* Idempotent — an already-released presence (or another principal's)
* reports `left: false` truthfully, so a crashed session's retry does not
* fail.
*/
leave(orgId: string, roomKey: string, presenceId: string): Promise<RoomLeaveResult>;
/**
* Register a session presence in a room
* (`POST /orgs/v1/:org_id/rooms/:room_key/presences`). Each call is a NEW
* presence with a new room-unique name — pass the returned `presence_id` on
* every later call to keep speaking as the SAME session (the server never
* infers a session from the credential). `opts.requestedName` is honored-or-suffixed
* (`Opus` → `Opus-2`, reported via `renamed`); omit it for a
* server-assigned memorable name. Expiry after ~1h of silence; any
* coordination call bumps liveness.
*/
registerPresence(orgId: string, roomKey: string, opts?: RegisterPresenceOptions): Promise<PresenceRegistration>;
/**
* List a room's live presences
* (`GET /orgs/v1/:org_id/rooms/:room_key/presences`) — who else is here,
* what they're working on, and their `active_claims` counts.
* `opts.includeExpired` adds history; `opts.name` is an exact-name lookup.
*/
listPresences(orgId: string, roomKey: string, opts?: ListPresencesOptions): Promise<RoomPresenceList>;
/**
* Read one presence
* (`GET /orgs/v1/:org_id/rooms/:room_key/presences/:presence_id`).
*/
getPresence(orgId: string, roomKey: string, presenceId: string): Promise<RoomPresence>;
/**
* Send a room-visible message
* (`POST /orgs/v1/:org_id/rooms/:room_key/messages`). `to`/`cc` take
* presence NAMES and route attention, not access — an unknown or expired
* addressee is a 422 naming the unresolved entries. Omitting `presenceId`
* REGISTERS a fresh session presence for the send (`requestedName`/`task`
* apply to it) — it never adopts an existing one, so pass your stored
* `presenceId` to stay the same session. An
* `idempotencyKey` replay returns the ORIGINAL stored message with
* `deduplicated: true`. Sends are bounded by the org-pooled
* `messages_per_day` quota.
*/
sendMessage(orgId: string, roomKey: string, input: SendRoomMessageInput): Promise<SentRoomMessage>;
/**
* Read a page of room messages
* (`GET /orgs/v1/:org_id/rooms/:room_key/messages`) — events-feed cursor
* semantics: ascending catch-up via `cursor`, or newest-first display via
* `order: "desc"` + `before`. List items carry `body_snippet` only; fetch
* the full body with {@link getMessage} (the view truncates, the data
* never does). Ascending reads with a resolved presence advance its read
* watermark (what `unread: true` filters against).
*/
listMessages(orgId: string, roomKey: string, opts?: ListRoomMessagesOptions): Promise<RoomMessagePage>;
/**
* Block until at least one matching message lands, or the timeout elapses
* (kygit-invite design D6/D7) — the agent's ear. Uses the gateway's held
* read (`wait=<seconds>` on the ascending message read) when it is
* observed to hold (a page carrying `waited_ms`), and degrades to
* client-side polling at `opts.pollMs` (default 5000ms) the instant a
* page comes back WITHOUT `waited_ms` — an older gateway that ignored the
* parameter. Decided by evidence on every read, not by a version check,
* so a single call is safe against either gateway.
*
* Silence is an answer: on timeout this RETURNS the last observed
* (empty) page with `settled: false`, never throws — the same contract as
* the shared `waitFor` helper. `live_presences` comes from the last
* page's own rider when the gateway held; otherwise it is fetched once at
* the end, best-effort (empty on any failure).
*/
waitForMessages(orgId: string, roomKey: string, opts?: WaitForRoomMessagesOptions): Promise<RoomMessageWaitResult>;
/**
* Read one message with its full body and per-recipient ack state
* (`GET /orgs/v1/:org_id/rooms/:room_key/messages/:message_id`).
*/
getMessage(orgId: string, roomKey: string, messageId: string): Promise<RoomMessage>;
/**
* Acknowledge a message addressed to you
* (`POST /orgs/v1/:org_id/rooms/:room_key/messages/:message_id/ack`).
* Recipients only (422 otherwise). Idempotent — a replay reports the
* ORIGINAL `acked_at` with `already_acked: true`.
*/
ackMessage(orgId: string, roomKey: string, messageId: string, opts?: AckRoomMessageOptions): Promise<RoomAckResult>;
/**
* Create an advisory claim
* (`POST /orgs/v1/:org_id/rooms/:room_key/claims`). GRANT-AND-REPORT: the
* 201 always succeeds and returns the complete `conflicts[]` — a claim
* never blocks anything, anywhere (deploys included). Auto-expires
* (`ttlSeconds` default 3600, max 86400); at most 32 active claims per
* presence. Omitting `presenceId` registers a fresh session presence for the
* claim — pass your stored `presenceId` to attribute it to this session.
*/
createClaim(orgId: string, roomKey: string, input: CreateRoomClaimInput): Promise<CreatedRoomClaim>;
/**
* List a room's active claims
* (`GET /orgs/v1/:org_id/rooms/:room_key/claims`).
* `opts.includeInactive` adds released/expired history (with
* `released_at`).
*/
listClaims(orgId: string, roomKey: string, opts?: ListRoomClaimsOptions): Promise<RoomClaimList>;
/**
* Release your own claim
* (`DELETE /orgs/v1/:org_id/rooms/:room_key/claims/:claim_id`). Holder's
* credential only. Idempotent — an already-released claim returns
* `already_released: true` with the original time.
*/
releaseClaim(orgId: string, roomKey: string, claimId: string): Promise<RoomClaimReleaseResult>;
/**
* Mint a Room Invite Key from the room the caller stands in
* (`POST /orgs/v1/:org_id/rooms/:room_key/invites`) — a single-use bearer
* key (`kri1_…`) whose redeemer becomes a permanent `viewer` of the org,
* the narrowest membership that can message (design D4: never `--role`,
* never wider, never auto-admitted as a vault writer). Requires
* `developer`+ (session, wallet, or admin credential — a grant key is
* refused, since a room invite confers org membership).
*
* `invite_id` and `master_secret` are generated LOCALLY (design D3): the
* gateway never sees `master_secret`, only the SHA-256 `auth_hash` this
* call derives and sends. The assembled key is returned exactly ONCE —
* nothing here or downstream persists it.
*/
invite(orgId: string, roomKey: string, opts?: RoomInviteMintOptions): Promise<RoomInviteMintResult>;
/**
* Claim a Room Invite Key (`POST /rooms/v1/invites/:invite_id/redeem`) —
* parses the key CLIENT-SIDE first, refusing a `kgh1_`/`kgi1_` vault key
* BY NAME (pointing at `run402 repos resume`/`run402 repos join`) before
* any network call (design D3). The redemption is an x402-PAID resource (the
* `room_seat` SKU, testnet only): the VERIFIED PAYER of that payment
* becomes the redeemer, so this call is sent through the client's paid
* fetch WITHOUT a bearer credential (`withAuth: false`) — no
* `SIGN-IN-WITH-X` header, and any cached control-plane session is
* deliberately not attached, exactly matching the gateway's own
* `403 ROOM_INVITE_REDEEM_REQUIRES_WALLET` refusal for a bearer-credentialed
* request at this route. A same-payer replay never pays twice
* (`deduplicated: true`, no second charge).
*/
join(key: string): Promise<RoomInviteJoinResult>;
/**
* Return a room-scoped sub-client with `(orgId, roomKey)` pre-bound.
* Synchronous — both ids are explicit. For a project's default room
* without knowing the org, use {@link forProject}.
*/
scoped(orgId: string, roomKey: string): ScopedRoom;
/**
* Resolve a project's DEFAULT room (`GET /projects/v1/:project_id` for the
* owning `org_id`, then {@link scoped}). The default room's key IS the
* project id verbatim — this is the zero-config rendezvous: two agents
* holding the same `run402.config.json` land in the same room with no
* shared setup.
*/
forProject(projectId: string): Promise<ScopedRoom>;
}
/**
* Terminal room-invite redeem refusals the gateway's own x402 paywall NEVER
* settles for (design D5 — the paywall buffers and settles only on a
* sub-400 response, so any of these five codes means no payment ever
* completed, refunded or otherwise). Live-proof defect B: without this, a
* spent/expired/revoked key, or a bearer credential presented to the redeem route
* route, surfaced as a generic `X402_PAYMENT_OUTCOME_AMBIGUOUS` — alarming
* and wrong, since the gateway had already answered with one of these and
* moved no funds. `node/paid-fetch.ts`'s default paid fetch recognizes a
* response carrying one of these codes, FROM this SDK's own configured API
* origin, as `"failed"` rather than `"ambiguous"` — letting the gateway's
* own typed envelope (not a synthesized payment-attempt error) reach the
* caller. Scoped by CODE, never by route: an arbitrary third-party paid URL
* — even one that echoes one of these exact strings — is never treated as
* this SDK's own origin, so its non-2xx after dispatch stays genuinely
* ambiguous, unchanged.
*/
export declare const ROOM_INVITE_TERMINAL_REFUSAL_CODES: ReadonlySet<string>;
/** True when `envelope.code` is one of {@link ROOM_INVITE_TERMINAL_REFUSAL_CODES}. */
export declare function isTerminalRoomInviteRefusal(envelope: Record<string, unknown> | null | undefined): boolean;
/**
* A room-scoped sub-client returned by {@link Rooms.scoped} /
* {@link Rooms.forProject}. The `(orgId, roomKey)` pair is bound at
* construction; instance operations drop both leading arguments.
*/
export declare class ScopedRoom {
private readonly rooms;
/** The org id this sub-client is bound to. Read-only. */
readonly orgId: string;
/** The room key this sub-client is bound to (a project id for a default room). Read-only. */
readonly roomKey: string;
constructor(rooms: Rooms, orgId: string, roomKey: string);
/** See {@link Rooms.get}. */
get(): Promise<RoomSummary>;
/** See {@link Rooms.leave}. */
leave(presenceId: string): Promise<RoomLeaveResult>;
/** See {@link Rooms.registerPresence}. */
registerPresence(opts?: RegisterPresenceOptions): Promise<PresenceRegistration>;
/** See {@link Rooms.listPresences}. */
listPresences(opts?: ListPresencesOptions): Promise<RoomPresenceList>;
/** See {@link Rooms.getPresence}. */
getPresence(presenceId: string): Promise<RoomPresence>;
/** See {@link Rooms.sendMessage}. */
sendMessage(input: SendRoomMessageInput): Promise<SentRoomMessage>;
/** See {@link Rooms.listMessages}. */
listMessages(opts?: ListRoomMessagesOptions): Promise<RoomMessagePage>;
/** See {@link Rooms.getMessage}. */
getMessage(messageId: string): Promise<RoomMessage>;
/** See {@link Rooms.waitForMessages}. */
waitForMessages(opts?: WaitForRoomMessagesOptions): Promise<RoomMessageWaitResult>;
/** See {@link Rooms.ackMessage}. */
ackMessage(messageId: string, opts?: AckRoomMessageOptions): Promise<RoomAckResult>;
/** See {@link Rooms.createClaim}. */
createClaim(input: CreateRoomClaimInput): Promise<CreatedRoomClaim>;
/** See {@link Rooms.listClaims}. */
listClaims(opts?: ListRoomClaimsOptions): Promise<RoomClaimList>;
/** See {@link Rooms.releaseClaim}. */
releaseClaim(claimId: string): Promise<RoomClaimReleaseResult>;
/** See {@link Rooms.invite} — pre-bound to this room. */
invite(opts?: RoomInviteMintOptions): Promise<RoomInviteMintResult>;
}
//# sourceMappingURL=rooms.d.ts.map