@openmarket/rooms-client 0.6.0 → 0.7.1

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.
@@ -132,7 +132,9 @@ export function isTriggeringEntry(entry, ownHandle, ownUserId, ownSeqs) {
132
132
  return false;
133
133
  if (isWebhookEntry(entry))
134
134
  return false;
135
- if (!entry.text?.trim())
135
+ // A poll is substantive content even with no caption, and a captionless
136
+ // poll REPLY is addressed at us like any other reply.
137
+ if (!entry.text?.trim() && entry.poll == null)
136
138
  return false;
137
139
  // Never trigger on our own message (we post as this handle/userId).
138
140
  const fromHandle = normalizeHandle(entry.handle).toLowerCase();
@@ -149,7 +151,9 @@ export function isTriggeringEntry(entry, ownHandle, ownUserId, ownSeqs) {
149
151
  // old mentionsAgent) fired on ordinary words for short handles like "may" /
150
152
  // "btc"; armed posting has no human review, so it triggers ONLY on an `@handle`
151
153
  // token. Empty handle never matches (mentionsAgentExplicit guards it).
152
- return mentionsAgentExplicit(entry.text, ownHandle);
154
+ // Mentions can live in the poll question too (member-authored content).
155
+ const mentionSource = [entry.text ?? "", entry.poll?.question ?? ""].filter(Boolean).join(" ");
156
+ return mentionsAgentExplicit(mentionSource, ownHandle);
153
157
  }
154
158
  /**
155
159
  * The newest qualifying trigger among entries strictly newer than
@@ -330,7 +334,7 @@ export function isHumanResetEntry(entry, ownHandle, ownUserId) {
330
334
  const me = normalizeHandle(ownHandle).toLowerCase();
331
335
  if (me.length > 0 && fromHandle === me)
332
336
  return false;
333
- return Boolean(entry.text?.trim());
337
+ return Boolean(entry.text?.trim()) || entry.poll != null;
334
338
  }
335
339
  /** Note a turn/post error against the kill switch; returns true once the room
336
340
  * should auto-disarm (>= maxConsecutiveErrors in a row). Mutates `state`. */
@@ -28,6 +28,11 @@ export type AgentMessage = {
28
28
  export interface ToolDispatchResult {
29
29
  isError?: true;
30
30
  content: string;
31
+ /** Full-fidelity render payload for in-process SURFACES (e.g. the TUI's
32
+ * backtest card). Present only on the streamed copy channels consume —
33
+ * the agent runtime strips it before the result reaches the model's
34
+ * messages or the conversation store, so it never costs context. */
35
+ artifact?: unknown;
31
36
  }
32
37
  export interface LlmTool {
33
38
  name: string;
@@ -18,6 +18,12 @@
18
18
  import type { RoomTopic, RoomTopicAttention, RoomTopicBucketState, RoomTopicsResultPayload } from "../shared/rooms-protocol.js";
19
19
  /** The reserved bucket key for untopicked (linear) messages. */
20
20
  export declare const UNTOPICKED_BUCKET = "";
21
+ /** Effective attention for a bucket: an elapsed timed mute reads as the
22
+ * base attention ('default'). The store expires timed mutes lazily (no
23
+ * deadline-triggered frame), so a hydrated inbox would otherwise stay
24
+ * muted past the deadline until the next hydration. A null or absent
25
+ * deadline keeps the stored attention (permanent mute). */
26
+ export declare function resolveTopicAttention(state: Pick<RoomTopicBucketState, "attention" | "muteUntil">, nowMs?: number): RoomTopicAttention;
21
27
  export interface TopicInboxItem {
22
28
  /** Bucket key: a topicId, or UNTOPICKED_BUCKET for the linear lane. */
23
29
  key: string;
@@ -17,6 +17,17 @@
17
17
  */
18
18
  /** The reserved bucket key for untopicked (linear) messages. */
19
19
  export const UNTOPICKED_BUCKET = "";
20
+ /** Effective attention for a bucket: an elapsed timed mute reads as the
21
+ * base attention ('default'). The store expires timed mutes lazily (no
22
+ * deadline-triggered frame), so a hydrated inbox would otherwise stay
23
+ * muted past the deadline until the next hydration. A null or absent
24
+ * deadline keeps the stored attention (permanent mute). */
25
+ export function resolveTopicAttention(state, nowMs = Date.now()) {
26
+ if (state.attention !== "mute" || state.muteUntil == null)
27
+ return state.attention;
28
+ const deadline = Date.parse(state.muteUntil);
29
+ return Number.isFinite(deadline) && deadline <= nowMs ? "default" : "mute";
30
+ }
20
31
  function emptyBucket() {
21
32
  return { lastSeq: 0, unreadCount: 0, cursor: 0, attention: "default" };
22
33
  }
@@ -111,7 +122,7 @@ export class RoomTopicInbox {
111
122
  return rows;
112
123
  }
113
124
  attentionFor(topicId) {
114
- return this.bucketFor(topicId).attention;
125
+ return resolveTopicAttention(this.bucketFor(topicId));
115
126
  }
116
127
  /** A registry mutation arrived (TOPIC_UPDATED, or a verb result echo). */
117
128
  applyTopicUpdated(topic) {
@@ -160,6 +171,10 @@ export class RoomTopicInbox {
160
171
  this.buckets.set(topicId, bucket);
161
172
  }
162
173
  bucket.attention = attention;
174
+ // The mirror knows only the new attention: a stale deadline from a
175
+ // previous timed mute must not expire it. The next hydration restores
176
+ // any server-side deadline.
177
+ delete bucket.muteUntil;
163
178
  }
164
179
  /**
165
180
  * The viewer saw a bucket up to seq (the view batcher owns the durable
@@ -188,7 +203,7 @@ export class RoomTopicInbox {
188
203
  channelUnread() {
189
204
  let total = 0;
190
205
  for (const bucket of this.buckets.values()) {
191
- if (bucket.attention === "mute")
206
+ if (resolveTopicAttention(bucket) === "mute")
192
207
  continue;
193
208
  total += bucket.unreadCount;
194
209
  }
@@ -205,7 +220,7 @@ export class RoomTopicInbox {
205
220
  inboxItems() {
206
221
  const items = [];
207
222
  const untopicked = this.bucketFor(UNTOPICKED_BUCKET);
208
- if (untopicked.unreadCount > 0 && untopicked.attention !== "mute") {
223
+ if (untopicked.unreadCount > 0 && resolveTopicAttention(untopicked) !== "mute") {
209
224
  items.push({
210
225
  key: UNTOPICKED_BUCKET,
211
226
  topic: undefined,
@@ -218,12 +233,12 @@ export class RoomTopicInbox {
218
233
  if (topic.mergedInto)
219
234
  continue;
220
235
  const state = this.bucketFor(topic.topicId);
221
- if (state.attention === "mute")
236
+ if (resolveTopicAttention(state) === "mute")
222
237
  continue;
223
238
  let aliasUnread = 0;
224
239
  for (const alias of this.aliasesOf(topic.topicId)) {
225
240
  const aliasState = this.bucketFor(alias);
226
- if (aliasState.attention === "mute")
241
+ if (resolveTopicAttention(aliasState) === "mute")
227
242
  continue;
228
243
  aliasUnread += aliasState.unreadCount;
229
244
  }
@@ -222,6 +222,19 @@ export interface RoomLedgerEntry {
222
222
  count: number;
223
223
  }> | undefined;
224
224
  editedAt?: string | number | Date | undefined;
225
+ /** Database-serialized edit counter: positive integer, strictly
226
+ * increasing per event row, incremented on every successful text or
227
+ * caption edit; absent means never edited. Orders caption edits where
228
+ * wall-clock editedAt stamps can tie or skew across store replicas.
229
+ * Missing on stores older than edit revisions. */
230
+ editRevision?: number | undefined;
231
+ /** Database-serialized card-mutation counter: positive integer,
232
+ * strictly increasing per event row, bumped on every unfurl card
233
+ * mutation (attach, edit-drop, purge); absent means cards were never
234
+ * mutated. Consumers apply card patches only at or above their known
235
+ * revision, making stale post-removal unfurl frames inert. Missing on
236
+ * stores older than unfurl revisions. */
237
+ unfurlsRevision?: number | undefined;
225
238
  deletedAt?: string | number | Date | undefined;
226
239
  deletedBy?: string | undefined;
227
240
  replyCount?: number | undefined;
@@ -230,6 +243,12 @@ export interface RoomLedgerEntry {
230
243
  pinnedBy?: string | null | undefined;
231
244
  pinnedAt?: string | number | Date | null | undefined;
232
245
  attachments?: RoomAttachment[] | undefined;
246
+ /** Counts-only poll summary when this message carries a poll. Additive:
247
+ * non-poll entries omit it, and live updates ride ENTRY_UPDATED. */
248
+ poll?: RoomPollSummary | null | undefined;
249
+ /** Server-rendered link unfurl cards. Additive: entries without links
250
+ * (and servers older than the unfurl worker) omit it. */
251
+ unfurls?: RoomUnfurl[] | undefined;
233
252
  roomRole?: RoomRole | null | undefined;
234
253
  metadata?: (Record<string, unknown> & Partial<RoomForwardMetadata> & Partial<RoomWebhookMetadata>) | undefined;
235
254
  }
@@ -279,6 +298,46 @@ export interface RoomAttachment {
279
298
  export type RoomAttachmentInput = RoomAttachment & {
280
299
  key: string;
281
300
  };
301
+ export interface RoomPollOption {
302
+ id: string;
303
+ text: string;
304
+ /** Vote count for this option. Counts only, never voter identities
305
+ * (voters are fetched on demand, exactly like reactors). */
306
+ count: number;
307
+ }
308
+ /**
309
+ * Counts-only poll summary denormalized onto the carrying message (a poll
310
+ * IS a message). Vote/close/edit updates ride the existing ENTRY_UPDATED
311
+ * frame with the refreshed summary; the server's side collections stay the
312
+ * authoritative copy.
313
+ */
314
+ export interface RoomPollSummary {
315
+ question: string;
316
+ options: RoomPollOption[];
317
+ multi: boolean;
318
+ /** ISO date when the poll auto-closes; null or absent means open-ended. */
319
+ closesAt?: string | null | undefined;
320
+ closed: boolean;
321
+ /** Store-serialized poll ordering revision, strictly increasing per
322
+ * poll: consumers reject frames below their known revision so
323
+ * bus-reordered vote/close updates cannot regress tallies. Present
324
+ * where the relay forwards it; missing on older relays. */
325
+ revision?: number | undefined;
326
+ }
327
+ /**
328
+ * Server-rendered link unfurl card on an entry. `url` is the canonical
329
+ * URL; `thumbnailKey` is the re-hosted copy in the attachments bucket,
330
+ * signed through the member-scoped attachment path. The origin's image URL
331
+ * never crosses the wire. Cards appear via ENTRY_UPDATED when the unfurl
332
+ * worker lands.
333
+ */
334
+ export interface RoomUnfurl {
335
+ url: string;
336
+ title?: string | undefined;
337
+ description?: string | undefined;
338
+ siteName?: string | undefined;
339
+ thumbnailKey?: string | undefined;
340
+ }
282
341
  /**
283
342
  * A registry-backed topic inside a channel. The id is permanent: rename
284
343
  * changes `name` only, and links (`om://topic/<topicId>`) keep resolving.
@@ -342,6 +401,21 @@ export interface RoomReadStatePayload {
342
401
  * Present when the store surfaces it and on ROOM_SET_ATTENTION acks
343
402
  * (which reply READ_STATE scoped to the one room, readState empty). */
344
403
  attention?: Record<string, RoomTopicAttention> | undefined;
404
+ /** Per-room timed-mute expiry (ISO date), keyed like attention. Rides
405
+ * beside a 'mute' attention entry when the mute carries an expiry; null
406
+ * marks an explicitly cleared or permanent mute. A lapsed expiry never
407
+ * appears: the server resolves it at read time and the attention value
408
+ * reads as its base ('default'). Missing on servers older than
409
+ * mute-with-duration. */
410
+ muteUntil?: Record<string, string | null> | undefined;
411
+ /** Per-room attention commit revision, keyed like attention: a
412
+ * database-serialized, strictly increasing counter per (user, room),
413
+ * present only for rooms whose attention has ever been committed.
414
+ * ORDERING CONTRACT: a consumer that has synced revision R for a room
415
+ * must ignore snapshot attention entries whose revision is <= R (a
416
+ * snapshot read before a newer commit can arrive after that commit's
417
+ * sync frame). Missing on stores older than attention revisions. */
418
+ attentionRevision?: Record<string, number> | undefined;
345
419
  /** Unread mention counts by room, server-computed from the notify
346
420
  * worker's ROOM_MENTION rows so badges survive reload and clear through
347
421
  * the ack path. Unlike readState/attention the map is SPARSE AND
@@ -864,6 +938,14 @@ export interface RoomTopicBucketState {
864
938
  unreadCount: number;
865
939
  cursor: number;
866
940
  attention: RoomTopicAttention;
941
+ /** Standing timed-mute deadline (ISO date; null = permanent mute).
942
+ * Present only on the room-wide '' bucket and only while its attention
943
+ * is an active 'mute'; never on topic buckets. Same semantics as the
944
+ * read-state muteUntil: the store expires timed mutes lazily, so
945
+ * consumers resolve an elapsed deadline back to the base attention
946
+ * ('default') at read time (see resolveTopicAttention). Missing on
947
+ * servers older than mute-with-duration. */
948
+ muteUntil?: string | null | undefined;
867
949
  }
868
950
  export interface RoomTopicResultPayload {
869
951
  room: string;
@@ -1,5 +1,6 @@
1
- import type { RoomDmConversation, RoomDmHistory, RoomDmSendResult, RoomDmUnreadConversation, RoomDoc, RoomDocAttribution, RoomDocChanges, RoomDocConflictHead, RoomDocEditingLease, RoomDocProvenance, RoomDocRevision, RoomDocRunRevertResult, RoomDocWriteResult, RoomFollow, RoomFollowResult, RoomFriend, RoomFriendRequests, RoomNotificationInboxResult, RoomPublicProfile, RoomRepliesReadResult, RoomRepliesResult, RoomSocialConfig, RoomSpaceRestorePlan, RoomSpaceRestoreReceipt, RoomSuggestion, RoomSuggestionRequest } from "./social-types.js";
2
- export type { RoomAttachment, RoomDmConversation, RoomDmHistory, RoomDmMessage, RoomDmParticipant, RoomDmSendResult, RoomDmUnreadConversation, RoomDocAttribution, RoomDocEditingLease, RoomDocMergeRegion, RoomDocRevisionSource, RoomDocRunRevertResult, RoomFollow, RoomFollowResult, RoomFriend, RoomFriendRequest, RoomFriendRequests, RoomNotificationInboxItem, RoomNotificationInboxResult, RoomNotificationKind, RoomNotificationMessageData, RoomPublicProfile, RoomRepliesReadResult, RoomRepliesResult, RoomReplyInboxItem, RoomSocialConfig, RoomSpaceRestoreAction, RoomSpaceRestoreActionKind, RoomSpaceRestorePlan, RoomSpaceRestoreReceipt, RoomSpaceRestoreSummary, RoomSuggestion, RoomSuggestionIntent, RoomSuggestionRequest, RoomSuggestionSource, } from "./social-types.js";
1
+ import type { RoomTopicAttention } from "./shared/rooms-protocol.js";
2
+ import type { RoomBookmarkSaved, RoomBookmarksResult, RoomDmConversation, RoomDmHistory, RoomDmSendResult, RoomDmUnreadConversation, RoomDoc, RoomDocAttribution, RoomDocChanges, RoomDocConflictHead, RoomDocEditingLease, RoomDocProvenance, RoomDocRevision, RoomDocRunRevertResult, RoomDocWriteResult, RoomFollow, RoomFollowResult, RoomFriend, RoomFriendRequests, RoomNotificationInboxResult, RoomPollVotersResult, RoomPublicProfile, RoomRepliesReadResult, RoomRepliesResult, RoomRestEvent, RoomSocialConfig, RoomSpaceRestorePlan, RoomSpaceRestoreReceipt, RoomSuggestion, RoomSuggestionRequest, RoomUserBlock, RoomUserBlocksResult } from "./social-types.js";
3
+ export type { RoomAttachment, RoomBookmark, RoomBookmarkSaved, RoomBookmarksResult, RoomDmConversation, RoomDmHistory, RoomDmMessage, RoomDmParticipant, RoomDmSendResult, RoomDmUnreadConversation, RoomDocAttribution, RoomDocEditingLease, RoomDocMergeRegion, RoomDocRevisionSource, RoomDocRunRevertResult, RoomFollow, RoomFollowResult, RoomFriend, RoomFriendRequest, RoomFriendRequests, RoomNotificationInboxItem, RoomNotificationInboxResult, RoomNotificationKind, RoomNotificationMessageData, RoomNotifyQuietHours, RoomPollOptionVoters, RoomPollVotersResult, RoomPublicProfile, RoomRepliesReadResult, RoomRepliesResult, RoomReplyInboxItem, RoomRestEvent, RoomSocialConfig, RoomSpaceNotificationOverride, RoomSpaceRestoreAction, RoomSpaceRestoreActionKind, RoomSpaceRestorePlan, RoomSpaceRestoreReceipt, RoomSpaceRestoreSummary, RoomSuggestion, RoomSuggestionIntent, RoomSuggestionRequest, RoomSuggestionSource, RoomsNotificationSettings, RoomUserBlock, RoomUserBlocksResult, } from "./social-types.js";
3
4
  /** Largest per-file size any tier accepts (plus tier). The server enforces
4
5
  * the per-tier caps (free 10MB/file, plus 25MB/file) and daily quotas; this
5
6
  * constant only pre-flights uploads no tier could ever accept. */
@@ -254,12 +255,20 @@ export interface RoomSpaceInvite {
254
255
  invitedBy?: string;
255
256
  role: "admin" | "member";
256
257
  createdAt?: string;
258
+ /** Expiry deadline (ISO date); null or absent means the invite stands
259
+ * until accepted or declined. Additive (0.6.0 servers). */
260
+ expiresAt?: string | null;
257
261
  }
258
- /** Create (or refresh) a pending invitation. Membership needs their accept. */
262
+ /** Create (or refresh) a pending invitation. Membership needs their accept.
263
+ * `expiresAt` (ISO date, future, at most 30 days out) puts a deadline on
264
+ * it; re-inviting without one clears any previous deadline. Fail-closed on
265
+ * servers older than 0.6.0: they persist the invite but drop the deadline,
266
+ * so a deadline the echo omits revokes the invite and throws. */
259
267
  export declare function createSpaceInvite(config: RoomSocialConfig, spaceId: string, input: {
260
268
  targetUserId?: string;
261
269
  targetUsername?: string;
262
270
  role?: "admin" | "member";
271
+ expiresAt?: string;
263
272
  }): Promise<RoomSpaceInvite>;
264
273
  /** The caller's own pending invitations. */
265
274
  export declare function listMySpaceInvites(config: RoomSocialConfig): Promise<RoomSpaceInvite[]>;
@@ -274,10 +283,22 @@ export declare function acceptSpaceInvite(config: RoomSocialConfig, inviteId: st
274
283
  export declare function declineSpaceInvite(config: RoomSocialConfig, inviteId: string): Promise<{
275
284
  declined?: boolean;
276
285
  }>;
277
- /** Mint (or rotate) the shareable join code; plaintext comes back once. */
278
- export declare function setSpaceJoinCode(config: RoomSocialConfig, spaceId: string): Promise<{
286
+ /** Mint (or rotate) the shareable join code; plaintext comes back once.
287
+ * Optional caps: `expiresAt` (ISO date, future, at most 30 days out) and
288
+ * `maxUses` (1..10000). Rotation is a fresh grant: the new code starts at
289
+ * zero uses and takes exactly the caps given here (omitted caps clear the
290
+ * previous ones). Servers older than 0.6.0 echo neither cap field; they
291
+ * mint the code but drop the caps, so a requested cap the echo omits
292
+ * fail-closes: the fresh code is revoked and the call throws rather than
293
+ * hand back a live UNLIMITED code the caller believes capped. */
294
+ export declare function setSpaceJoinCode(config: RoomSocialConfig, spaceId: string, options?: {
295
+ expiresAt?: string | undefined;
296
+ maxUses?: number | undefined;
297
+ }): Promise<{
279
298
  spaceId: string;
280
299
  code: string;
300
+ expiresAt?: string | null;
301
+ maxUses?: number | null;
281
302
  }>;
282
303
  export declare function clearSpaceJoinCode(config: RoomSocialConfig, spaceId: string): Promise<{
283
304
  cleared?: boolean;
@@ -411,6 +432,168 @@ export declare function listRoomNotifications(config: RoomSocialConfig, options?
411
432
  unread?: boolean | undefined;
412
433
  signal?: AbortSignal | undefined;
413
434
  }): Promise<RoomNotificationInboxResult>;
435
+ /**
436
+ * Mark a room unread from `seq` (Discord-style): the room-wide read cursor
437
+ * drops to seq - 1 (server-clamped at 0) so the unread divider and pill
438
+ * return. The one sanctioned cursor-regression path; mention rows an
439
+ * earlier ack swept stay swept, and nothing is re-notified.
440
+ */
441
+ export declare function markRoomUnread(config: RoomSocialConfig, room: string, seq: number): Promise<{
442
+ success: boolean;
443
+ }>;
444
+ /**
445
+ * Set the caller's room-level attention over REST, the path that carries
446
+ * mute-with-duration: `untilMs` (epoch ms, valid only with 'mute') stamps
447
+ * the expiry that read state later surfaces as `muteUntil`; the mute lapses
448
+ * server-side and reads as 'default' afterwards. Every write settles the
449
+ * expiry, so follow/default (and a plain permanent mute) clear a previous
450
+ * one. The pinned ROOM_SET_ATTENTION frame (setRoomAttentionOnClient)
451
+ * predates `untilMs` and stays the live-session path for permanent
452
+ * postures; servers older than mute-with-duration strip the field and land
453
+ * the mute permanent, so a requested `untilMs` the echo omits is a DROPPED
454
+ * DEADLINE. By default that fail-closes here: the attention is reset and
455
+ * the call throws rather than leave an unbounded mute the caller believes
456
+ * timed. Callers with a richer fail-closed seam of their own opt out via
457
+ * `onDroppedDeadline: "resolve"` and own the handling (see the option).
458
+ * Echoes the persisted attention; `muteUntil` is the ISO expiry, null when
459
+ * none stands (which, under "resolve", is how a dropped deadline reads).
460
+ */
461
+ export declare function setRoomAttentionRest(config: RoomSocialConfig, room: string, attention: RoomTopicAttention, options?: {
462
+ untilMs?: number | undefined;
463
+ /** What to do when a requested `untilMs` comes back without an echoed
464
+ * `muteUntil` (the server predates timed mutes and landed the mute
465
+ * PERMANENT). Default "undo-and-throw": reset attention to 'default'
466
+ * and throw RoomSocialError 502, so no unbounded mute stands. Pass
467
+ * "resolve" ONLY when the caller implements its own dropped-deadline
468
+ * handling: the call then performs no undo write and no throw, and
469
+ * resolves with `muteUntil: null` for the caller to act on. Canonical
470
+ * example: the GUI session seam, which undoes to the last
471
+ * WS-confirmed attention, richer than this library's
472
+ * undo-to-default. */
473
+ onDroppedDeadline?: "undo-and-throw" | "resolve" | undefined;
474
+ }): Promise<{
475
+ attention: RoomTopicAttention;
476
+ muteUntil: string | null;
477
+ }>;
478
+ /**
479
+ * Save a message for later (personal, per-user; nothing broadcasts).
480
+ * Idempotent: re-saving an already bookmarked event echoes the standing
481
+ * row with its original createdAt.
482
+ */
483
+ export declare function addRoomBookmark(config: RoomSocialConfig, room: string, seq: number): Promise<RoomBookmarkSaved>;
484
+ export declare function removeRoomBookmark(config: RoomSocialConfig, room: string, seq: number): Promise<{
485
+ removed: boolean;
486
+ }>;
487
+ /**
488
+ * The caller's bookmarks across every room (GET /users/bookmarks), newest
489
+ * first with the notification inbox's composite cursor: pass a page's
490
+ * lastCursor and lastCursorId back TOGETHER for the next page. Rows in
491
+ * rooms the caller can no longer read are subtracted server-side.
492
+ */
493
+ export declare function listUserBookmarks(config: RoomSocialConfig, options?: {
494
+ limit?: number | undefined;
495
+ /** Feed a page's lastCursor back here (explicit undefined reads as
496
+ * "first page", so `page.lastCursor ?? undefined` composes under
497
+ * exactOptionalPropertyTypes). */
498
+ cursor?: string | undefined;
499
+ cursorId?: string | undefined;
500
+ signal?: AbortSignal | undefined;
501
+ }): Promise<RoomBookmarksResult>;
502
+ /**
503
+ * The caller's user-block list (GET /users/blocks), newest first with the
504
+ * same composite-cursor paging as the notification inbox. Blocks are
505
+ * directional and the blocker is always the verified caller.
506
+ */
507
+ export declare function getUserBlocks(config: RoomSocialConfig, options?: {
508
+ limit?: number | undefined;
509
+ cursor?: string | undefined;
510
+ cursorId?: string | undefined;
511
+ signal?: AbortSignal | undefined;
512
+ }): Promise<RoomUserBlocksResult>;
513
+ /** Block a user by userId (PUT, idempotent: re-blocking echoes the standing
514
+ * row with its original createdAt). Server-enforced effects: DM doors shut
515
+ * both directions, friend requests refuse, and the blocked sender produces
516
+ * no notifications for the caller. Room visibility is unchanged. */
517
+ export declare function blockUser(config: RoomSocialConfig, userId: string): Promise<RoomUserBlock>;
518
+ /** Unblock a user (idempotent: unblocking a non-blocked user is a no-op). */
519
+ export declare function unblockUser(config: RoomSocialConfig, userId: string): Promise<{
520
+ success: boolean;
521
+ }>;
522
+ /**
523
+ * Post a poll message. There is no separate create route and no WS input
524
+ * path (the relay's post frame carries text only): a poll rides the normal
525
+ * REST append as the strict body's `poll` block. `options` are option TEXT
526
+ * (2..10 entries; ids are minted server-side) and `closesAt` is an ISO
527
+ * deadline that must sit in the future. `text` is the optional caption;
528
+ * absent is fine, the poll IS the content. Echoes the created store event
529
+ * row with its counts-only summary; the room hears the same message
530
+ * through the ordinary live echo. Additive (0.6.0 servers): older stores
531
+ * reject the unknown block.
532
+ */
533
+ export declare function createRoomPoll(config: RoomSocialConfig, room: string, input: {
534
+ question: string;
535
+ options: string[];
536
+ multi?: boolean | undefined;
537
+ closesAt?: string | undefined;
538
+ text?: string | undefined;
539
+ /** Exact-case workspace id: lets a COLD relay replica materialize a
540
+ * virtual workspace room without guessing the id's casing from the
541
+ * lowercased room name (owners resolve server-side; non-owner first
542
+ * writers need this). */
543
+ workspaceId?: string | undefined;
544
+ /** Route into a topic lane like any other append. */
545
+ topicId?: string | undefined;
546
+ /** Post the poll as a threaded reply. */
547
+ replyToSeq?: number | undefined;
548
+ /** Server-side idempotency: retries after an ambiguous failure reuse
549
+ * the key instead of minting a duplicate poll. */
550
+ clientMsgId?: string | undefined;
551
+ }): Promise<RoomRestEvent>;
552
+ /**
553
+ * Vote on a poll (the poll is keyed by its carrying message's seq). Echoes
554
+ * the store's public event row (RoomRestEvent, not the relay's mapped
555
+ * ledger entry) with the refreshed counts-only summary; the room hears the
556
+ * same update over ENTRY_UPDATED. Single-choice polls replace the caller's
557
+ * previous ballot; multi-choice polls union into it.
558
+ */
559
+ export declare function voteRoomPoll(config: RoomSocialConfig, room: string, seq: number, optionIds: string[]): Promise<RoomRestEvent>;
560
+ /** Clear the caller's vote. Echoes the refreshed store event row. */
561
+ export declare function unvoteRoomPoll(config: RoomSocialConfig, room: string, seq: number): Promise<RoomRestEvent>;
562
+ /** Close a poll (idempotent on an already-closed poll). Echoes the store
563
+ * event row with `poll.closed` set. */
564
+ export declare function closeRoomPoll(config: RoomSocialConfig, room: string, seq: number): Promise<RoomRestEvent>;
565
+ /** Edit a poll's question/options (author-only, and locked once any vote
566
+ * exists or the poll closes; at least one field required). Options are
567
+ * full replacement TEXT, like creation. */
568
+ /** Purge an event's frozen unfurl cards without editing or deleting the
569
+ * message. Author or a delete-any moderation power; the store unsets the
570
+ * cards, schedules thumbnail cleanup, and returns the mutated event for
571
+ * ENTRY_UPDATED fan-out. An edit that removes a card's URL from the text
572
+ * drops that card automatically; this route is the explicit purge for
573
+ * everything else (a capability URL that must go without rewording the
574
+ * message, a moderator acting on someone else's post). */
575
+ export declare function unsetRoomEventUnfurls(config: RoomSocialConfig, room: string, seq: number): Promise<void>;
576
+ /** Edit an event's text over REST: the cold-replica-safe mirror of the WS
577
+ * EDIT frame, and the ONE shape the WS path cannot express: an empty text
578
+ * on a poll-bearing message legally clears the optional caption (the poll
579
+ * stays the content; the store rejects empty edits on plain messages).
580
+ * Viewers reconcile through the store's ENTRY_UPDATED fan-out exactly
581
+ * like the REST poll verbs. */
582
+ export declare function editRoomEventRest(config: RoomSocialConfig, room: string, seq: number, text: string): Promise<void>;
583
+ export declare function editRoomPoll(config: RoomSocialConfig, room: string, seq: number, patch: {
584
+ question?: string | undefined;
585
+ options?: string[] | undefined;
586
+ }): Promise<RoomRestEvent>;
587
+ /**
588
+ * Voter identities, on demand (the entry's summary carries counts only).
589
+ * Grouped per option; `optionId` narrows to one option and `limit` caps the
590
+ * identities per option (counts stay exact).
591
+ */
592
+ export declare function listRoomPollVoters(config: RoomSocialConfig, room: string, seq: number, options?: {
593
+ optionId?: string | undefined;
594
+ limit?: number | undefined;
595
+ signal?: AbortSignal | undefined;
596
+ }): Promise<RoomPollVotersResult>;
414
597
  export declare function suggestRooms(config: RoomSocialConfig, request: RoomSuggestionRequest, options?: {
415
598
  signal?: AbortSignal;
416
599
  }): Promise<RoomSuggestion[]>;