@openmarket/rooms-client 0.1.0
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/LICENSE +201 -0
- package/README.md +25 -0
- package/dist/agent/armed-mode.d.ts +187 -0
- package/dist/agent/armed-mode.js +306 -0
- package/dist/agent/clients/common.d.ts +70 -0
- package/dist/agent/clients/common.js +46 -0
- package/dist/agent/lane-draft.d.ts +15 -0
- package/dist/agent/lane-draft.js +43 -0
- package/dist/agent/lane-prompts.d.ts +102 -0
- package/dist/agent/lane-prompts.js +120 -0
- package/dist/agent/library-context.d.ts +38 -0
- package/dist/agent/library-context.js +57 -0
- package/dist/agent/library-model.d.ts +41 -0
- package/dist/agent/library-model.js +173 -0
- package/dist/agent/room-context.d.ts +151 -0
- package/dist/agent/room-context.js +251 -0
- package/dist/agent/sidebar-model.d.ts +86 -0
- package/dist/agent/sidebar-model.js +162 -0
- package/dist/agent/topic-inbox.d.ts +119 -0
- package/dist/agent/topic-inbox.js +266 -0
- package/dist/agent/topic-view-batcher.d.ts +54 -0
- package/dist/agent/topic-view-batcher.js +115 -0
- package/dist/client.d.ts +421 -0
- package/dist/client.js +1428 -0
- package/dist/doc-reconcile.d.ts +100 -0
- package/dist/doc-reconcile.js +110 -0
- package/dist/doc-uri.d.ts +29 -0
- package/dist/doc-uri.js +55 -0
- package/dist/endpoints.d.ts +9 -0
- package/dist/endpoints.js +13 -0
- package/dist/jwt.d.ts +10 -0
- package/dist/jwt.js +51 -0
- package/dist/library-publisher.d.ts +52 -0
- package/dist/library-publisher.js +49 -0
- package/dist/merge3.d.ts +50 -0
- package/dist/merge3.js +280 -0
- package/dist/names.d.ts +6 -0
- package/dist/names.js +35 -0
- package/dist/shared/emoji-data.d.ts +22 -0
- package/dist/shared/emoji-data.js +8834 -0
- package/dist/shared/mentions.d.ts +6 -0
- package/dist/shared/mentions.js +32 -0
- package/dist/shared/rooms-protocol.d.ts +1183 -0
- package/dist/shared/rooms-protocol.js +160 -0
- package/dist/social-client.d.ts +361 -0
- package/dist/social-client.js +686 -0
- package/dist/social-types.d.ts +338 -0
- package/dist/social-types.js +1 -0
- package/dist/suggestion-attribution.d.ts +10 -0
- package/dist/suggestion-attribution.js +56 -0
- package/dist/topic-uri.d.ts +26 -0
- package/dist/topic-uri.js +40 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/dist/ws-client.d.ts +115 -0
- package/dist/ws-client.js +491 -0
- package/package.json +180 -0
- package/src/agent/armed-mode.ts +368 -0
- package/src/agent/clients/common.ts +91 -0
- package/src/agent/lane-draft.ts +47 -0
- package/src/agent/lane-prompts.ts +267 -0
- package/src/agent/library-context.ts +97 -0
- package/src/agent/library-model.ts +210 -0
- package/src/agent/room-context.ts +351 -0
- package/src/agent/sidebar-model.ts +235 -0
- package/src/agent/topic-inbox.ts +297 -0
- package/src/agent/topic-view-batcher.ts +134 -0
- package/src/client.ts +2331 -0
- package/src/doc-reconcile.ts +160 -0
- package/src/doc-uri.ts +83 -0
- package/src/endpoints.ts +14 -0
- package/src/jwt.ts +59 -0
- package/src/library-publisher.ts +93 -0
- package/src/merge3.ts +326 -0
- package/src/names.ts +44 -0
- package/src/shared/emoji-data.ts +8868 -0
- package/src/shared/mentions.ts +32 -0
- package/src/shared/rooms-protocol.ts +1339 -0
- package/src/social-client.ts +1287 -0
- package/src/social-types.ts +376 -0
- package/src/suggestion-attribution.ts +83 -0
- package/src/topic-uri.ts +64 -0
- package/src/types.ts +83 -0
- package/src/version.ts +2 -0
- package/src/ws-client.ts +611 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import type { RoomLedgerSecretPayload } from "./shared/rooms-protocol.js";
|
|
2
|
+
|
|
3
|
+
export interface RoomSocialConfig {
|
|
4
|
+
apiUrl: string;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type RoomSuggestionSource = "init" | "alert_create" | "market_lookup" | "status" | "digest";
|
|
9
|
+
|
|
10
|
+
export interface RoomSuggestionIntent {
|
|
11
|
+
symbols?: string[];
|
|
12
|
+
entities?: string[];
|
|
13
|
+
categories?: string[];
|
|
14
|
+
roomHints?: string[];
|
|
15
|
+
reason?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface RoomSuggestionRequest {
|
|
19
|
+
source: RoomSuggestionSource;
|
|
20
|
+
intent?: RoomSuggestionIntent;
|
|
21
|
+
limit?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface RoomSuggestion {
|
|
25
|
+
room: string;
|
|
26
|
+
title: string;
|
|
27
|
+
proof: {
|
|
28
|
+
online: number;
|
|
29
|
+
heat: "surging" | "active_now";
|
|
30
|
+
latestActivity?: string;
|
|
31
|
+
followingOnline?: number;
|
|
32
|
+
followingHandles?: string[];
|
|
33
|
+
friendsOnline?: number;
|
|
34
|
+
friendHandles?: string[];
|
|
35
|
+
};
|
|
36
|
+
copy: string;
|
|
37
|
+
suggestionId: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface RoomFollow {
|
|
41
|
+
userId: string;
|
|
42
|
+
profileUrl?: string | undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface RoomFollowTarget {
|
|
46
|
+
userId: string;
|
|
47
|
+
username: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface RoomFollowResult {
|
|
51
|
+
target: RoomFollowTarget;
|
|
52
|
+
isNewFollow?: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface RoomAttachment {
|
|
56
|
+
/** Object key under room-attachments/; clients sign it at render time. */
|
|
57
|
+
key: string;
|
|
58
|
+
name: string;
|
|
59
|
+
size: number;
|
|
60
|
+
mime: string;
|
|
61
|
+
scanStatus?: "pending" | "clean" | "held" | undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface RoomAttachmentUpload {
|
|
65
|
+
/** The stored attachment plus its first signed URL, for immediate render. */
|
|
66
|
+
attachment: RoomAttachment & { url: string; expiresAt: string | null };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface RoomPublicProfile {
|
|
70
|
+
userId: string;
|
|
71
|
+
displayName?: string | undefined;
|
|
72
|
+
username?: string | undefined;
|
|
73
|
+
handle?: string | undefined;
|
|
74
|
+
profileDescription?: string | undefined;
|
|
75
|
+
profileUrl?: string | undefined;
|
|
76
|
+
followerCount?: number | undefined;
|
|
77
|
+
memberSince?: string | number | null | undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface RoomFriend {
|
|
81
|
+
friendshipId: string;
|
|
82
|
+
userId: string;
|
|
83
|
+
username: string;
|
|
84
|
+
acceptedAt: string | number;
|
|
85
|
+
lastMessageAt?: string | number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface RoomFriendRequest {
|
|
89
|
+
friendshipId: string;
|
|
90
|
+
userId: string;
|
|
91
|
+
username: string;
|
|
92
|
+
createdAt: string | number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface RoomFriendRequests {
|
|
96
|
+
sent: RoomFriendRequest[];
|
|
97
|
+
received: RoomFriendRequest[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface RoomDmParticipant {
|
|
101
|
+
userId: string;
|
|
102
|
+
username: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface RoomDmConversation {
|
|
106
|
+
conversationId: string;
|
|
107
|
+
participant: RoomDmParticipant;
|
|
108
|
+
unreadCount: number;
|
|
109
|
+
lastMessage?: RoomDmMessage;
|
|
110
|
+
updatedAt?: string | number;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface RoomDmMessage {
|
|
114
|
+
id?: string;
|
|
115
|
+
_id?: string;
|
|
116
|
+
messageId?: string;
|
|
117
|
+
conversationId?: string;
|
|
118
|
+
senderId?: string;
|
|
119
|
+
userId?: string;
|
|
120
|
+
senderUsername?: string;
|
|
121
|
+
username?: string;
|
|
122
|
+
content: string;
|
|
123
|
+
type?: string;
|
|
124
|
+
timestamp?: string | number;
|
|
125
|
+
createdAt?: string | number;
|
|
126
|
+
/** Sealed DM: content is '' and the opaque envelope rides here. */
|
|
127
|
+
secret?: RoomLedgerSecretPayload | null;
|
|
128
|
+
metadata?: Record<string, unknown>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface RoomDmHistory {
|
|
132
|
+
conversationId: string;
|
|
133
|
+
participant: RoomDmParticipant;
|
|
134
|
+
messages: RoomDmMessage[];
|
|
135
|
+
hasMore: boolean;
|
|
136
|
+
cursor?: number;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface RoomDmUnreadConversation extends RoomDmHistory {
|
|
140
|
+
unreadCount: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface RoomDmSendResult {
|
|
144
|
+
conversationId: string;
|
|
145
|
+
participant: RoomDmParticipant;
|
|
146
|
+
message: RoomDmMessage;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface RoomReplyInboxItem {
|
|
150
|
+
id?: string;
|
|
151
|
+
roomId: string;
|
|
152
|
+
replySeq: number;
|
|
153
|
+
parentSeq: number;
|
|
154
|
+
senderId?: string;
|
|
155
|
+
senderHandle: string;
|
|
156
|
+
parentPreview: string;
|
|
157
|
+
replyPreview: string;
|
|
158
|
+
readAt?: string | number | null;
|
|
159
|
+
createdAt?: string | number;
|
|
160
|
+
updatedAt?: string | number;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface RoomRepliesResult {
|
|
164
|
+
replies: RoomReplyInboxItem[];
|
|
165
|
+
unreadCount: number;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface RoomRepliesReadResult {
|
|
169
|
+
markedRead: number;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** A living markdown doc scoped to a space (head state; content on demand). */
|
|
173
|
+
export interface RoomDoc {
|
|
174
|
+
docId: string;
|
|
175
|
+
spaceId: string;
|
|
176
|
+
path: string;
|
|
177
|
+
title: string;
|
|
178
|
+
headRev: number;
|
|
179
|
+
headSha: string;
|
|
180
|
+
headNote: string;
|
|
181
|
+
headAuthorId: string;
|
|
182
|
+
headAuthorHandle: string;
|
|
183
|
+
headIsAgent: boolean;
|
|
184
|
+
headAt: string;
|
|
185
|
+
status: "active" | "archived";
|
|
186
|
+
aclMode: "space" | "owner";
|
|
187
|
+
restrictedToRoomId: string | null;
|
|
188
|
+
discussionRoomId: string | null;
|
|
189
|
+
createdByUserId: string;
|
|
190
|
+
createdByHandle: string;
|
|
191
|
+
sizeBytes: number;
|
|
192
|
+
spaceDocSeq: number;
|
|
193
|
+
createdAt?: string;
|
|
194
|
+
updatedAt?: string;
|
|
195
|
+
content?: string;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export interface RoomDocProvenance {
|
|
199
|
+
roomId?: string | undefined;
|
|
200
|
+
seqFrom?: number | undefined;
|
|
201
|
+
seqTo?: number | undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Which surface issued a doc write: the rooms GUI editor, an agent doc verb,
|
|
206
|
+
* the local disk mirror (library sync), or the TUI/CLI flows. Self-declared
|
|
207
|
+
* attribution for the history view (same trust model as `asAgent`), never
|
|
208
|
+
* enforcement.
|
|
209
|
+
*/
|
|
210
|
+
export type RoomDocRevisionSource = "gui" | "agent" | "mirror" | "cli";
|
|
211
|
+
|
|
212
|
+
/** Optional attribution rider accepted by every doc write. */
|
|
213
|
+
export interface RoomDocAttribution {
|
|
214
|
+
/** Phase 1 agent credential id (`agcr_*`) when an agent authored the write. */
|
|
215
|
+
credentialId?: string | undefined;
|
|
216
|
+
/** Groups every revision one agent run produced. */
|
|
217
|
+
runId?: string | undefined;
|
|
218
|
+
/** Surface that issued the write. */
|
|
219
|
+
source?: RoomDocRevisionSource | undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface RoomDocRevision {
|
|
223
|
+
docId: string;
|
|
224
|
+
rev: number;
|
|
225
|
+
op: string;
|
|
226
|
+
authorUserId: string;
|
|
227
|
+
authorHandle: string;
|
|
228
|
+
isAgent: boolean;
|
|
229
|
+
baseRev: number;
|
|
230
|
+
sha: string;
|
|
231
|
+
sizeBytes: number;
|
|
232
|
+
note: string;
|
|
233
|
+
noteSource: "human" | "agent" | "auto";
|
|
234
|
+
/** Attribution rider (null on pre-attribution revisions and older servers). */
|
|
235
|
+
credentialId?: string | null;
|
|
236
|
+
runId?: string | null;
|
|
237
|
+
source?: RoomDocRevisionSource | null;
|
|
238
|
+
provenance: RoomDocProvenance | null;
|
|
239
|
+
pathAfter: string | null;
|
|
240
|
+
revertOf: number | null;
|
|
241
|
+
recoveryPlanId?: string | null;
|
|
242
|
+
restorePoint?: string | null;
|
|
243
|
+
statusAfter?: "active" | "archived" | null;
|
|
244
|
+
at: string;
|
|
245
|
+
content?: string;
|
|
246
|
+
diff?: string;
|
|
247
|
+
diffOmitted?: boolean;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export type RoomSpaceRestoreActionKind =
|
|
251
|
+
| "content"
|
|
252
|
+
| "archive"
|
|
253
|
+
| "restore"
|
|
254
|
+
| "content_and_status"
|
|
255
|
+
| "archive_created_after";
|
|
256
|
+
|
|
257
|
+
export interface RoomSpaceRestoreAction {
|
|
258
|
+
docId: string;
|
|
259
|
+
path: string;
|
|
260
|
+
kind: RoomSpaceRestoreActionKind;
|
|
261
|
+
currentRev: number;
|
|
262
|
+
targetRev: number | null;
|
|
263
|
+
currentStatus: "active" | "archived";
|
|
264
|
+
targetStatus: "active" | "archived";
|
|
265
|
+
currentAuthorUserId: string;
|
|
266
|
+
currentAuthorHandle: string;
|
|
267
|
+
currentAt: string;
|
|
268
|
+
targetAt: string | null;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export interface RoomSpaceRestoreSummary {
|
|
272
|
+
totalDocs: number;
|
|
273
|
+
totalActions: number;
|
|
274
|
+
contentRestores: number;
|
|
275
|
+
archives: number;
|
|
276
|
+
restores: number;
|
|
277
|
+
pathConflicts: number;
|
|
278
|
+
unchanged: number;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export interface RoomSpaceRestorePlan {
|
|
282
|
+
planId: string;
|
|
283
|
+
spaceId: string;
|
|
284
|
+
restoreAt: string;
|
|
285
|
+
baseCursor: number;
|
|
286
|
+
reason: string;
|
|
287
|
+
requestedByHandle: string;
|
|
288
|
+
status: "ready" | "applying" | "applied" | "failed" | "stale";
|
|
289
|
+
scope: "content_and_lifecycle";
|
|
290
|
+
scopeNote: string;
|
|
291
|
+
actions: RoomSpaceRestoreAction[];
|
|
292
|
+
summary: RoomSpaceRestoreSummary;
|
|
293
|
+
appliedCount: number;
|
|
294
|
+
expiresAt: string;
|
|
295
|
+
appliedAt: string | null;
|
|
296
|
+
resultingCursor: number | null;
|
|
297
|
+
error: string | null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export interface RoomSpaceRestoreReceipt {
|
|
301
|
+
planId: string;
|
|
302
|
+
spaceId: string;
|
|
303
|
+
restoreAt: string;
|
|
304
|
+
reason: string;
|
|
305
|
+
status: "applied";
|
|
306
|
+
appliedActions: number;
|
|
307
|
+
resultingCursor: number;
|
|
308
|
+
appliedAt: string;
|
|
309
|
+
summary: RoomSpaceRestoreSummary;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Resumable per-space change feed page. `latestSeq` is the space's current
|
|
313
|
+
* head cursor: `cursor === latestSeq` means the caller is fully in sync. */
|
|
314
|
+
export interface RoomDocChanges {
|
|
315
|
+
changes: RoomDoc[];
|
|
316
|
+
cursor: number;
|
|
317
|
+
latestSeq: number;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export interface RoomDocConflictHead {
|
|
321
|
+
rev: number;
|
|
322
|
+
sha: string;
|
|
323
|
+
content: string;
|
|
324
|
+
note: string;
|
|
325
|
+
authorHandle: string;
|
|
326
|
+
isAgent: boolean;
|
|
327
|
+
at: string;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export interface RoomDocWriteResult {
|
|
331
|
+
doc: RoomDoc;
|
|
332
|
+
rev: number;
|
|
333
|
+
unchanged: boolean;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** One diverging chunk from a three-way merge (line arrays, no separators). */
|
|
337
|
+
export interface RoomDocMergeRegion {
|
|
338
|
+
base: string[];
|
|
339
|
+
mine: string[];
|
|
340
|
+
theirs: string[];
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Outcome of a selective run revert. A merge conflict is OUTCOME data, not
|
|
345
|
+
* an error: nothing was written, and the payload carries the three full
|
|
346
|
+
* contents (base = the run's final content, mine = the current head,
|
|
347
|
+
* theirs = the pre-run content) plus the diverging regions so the reconcile
|
|
348
|
+
* surface can mediate. `headRev` is the fence a mediated resolution
|
|
349
|
+
* publishes against.
|
|
350
|
+
*/
|
|
351
|
+
export type RoomDocRunRevertResult =
|
|
352
|
+
| { status: "reverted"; doc: RoomDoc; rev: number; unchanged: boolean }
|
|
353
|
+
| {
|
|
354
|
+
status: "conflict";
|
|
355
|
+
headRev: number;
|
|
356
|
+
runId: string;
|
|
357
|
+
base: string;
|
|
358
|
+
mine: string;
|
|
359
|
+
theirs: string;
|
|
360
|
+
regions: RoomDocMergeRegion[];
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* An editing-presence lease: the HINT that a user currently holds a dirty
|
|
365
|
+
* working copy of a doc on some device. Renewed by heartbeat, dropped on
|
|
366
|
+
* publish/discard, TTL-expired otherwise. Never a lock, never content;
|
|
367
|
+
* `isAgent` lets UIs class agent editors per the phase 3 presence rules.
|
|
368
|
+
*/
|
|
369
|
+
export interface RoomDocEditingLease {
|
|
370
|
+
docId: string;
|
|
371
|
+
spaceId: string;
|
|
372
|
+
userId: string;
|
|
373
|
+
handle: string;
|
|
374
|
+
isAgent: boolean;
|
|
375
|
+
expiresAt: string;
|
|
376
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { RoomSuggestion } from "./social-types.js";
|
|
2
|
+
|
|
3
|
+
export interface RoomSuggestionAttribution {
|
|
4
|
+
suggestionId: string;
|
|
5
|
+
hook: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const ROOM_SUGGESTION_ATTRIBUTION_TTL_MS = 15 * 60 * 1000;
|
|
9
|
+
|
|
10
|
+
const MAX_ROOM_SUGGESTION_ATTRIBUTIONS = 100;
|
|
11
|
+
const roomSuggestionAttributions = new Map<
|
|
12
|
+
string,
|
|
13
|
+
RoomSuggestionAttribution & { expiresAt: number }
|
|
14
|
+
>();
|
|
15
|
+
|
|
16
|
+
export function rememberRoomSuggestionAttribution(
|
|
17
|
+
suggestion: Pick<RoomSuggestion, "room" | "suggestionId">,
|
|
18
|
+
hook: string,
|
|
19
|
+
now = Date.now(),
|
|
20
|
+
): void {
|
|
21
|
+
const room = normalizeRoomKey(suggestion.room);
|
|
22
|
+
const suggestionId = suggestion.suggestionId.trim();
|
|
23
|
+
const normalizedHook = hook.trim();
|
|
24
|
+
if (!room || !suggestionId || !normalizedHook) return;
|
|
25
|
+
|
|
26
|
+
pruneExpiredRoomSuggestionAttributions(now);
|
|
27
|
+
while (
|
|
28
|
+
roomSuggestionAttributions.size >= MAX_ROOM_SUGGESTION_ATTRIBUTIONS &&
|
|
29
|
+
!roomSuggestionAttributions.has(room)
|
|
30
|
+
) {
|
|
31
|
+
const oldest = roomSuggestionAttributions.keys().next().value;
|
|
32
|
+
if (!oldest) break;
|
|
33
|
+
roomSuggestionAttributions.delete(oldest);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
roomSuggestionAttributions.set(room, {
|
|
37
|
+
suggestionId,
|
|
38
|
+
hook: normalizedHook,
|
|
39
|
+
expiresAt: now + ROOM_SUGGESTION_ATTRIBUTION_TTL_MS,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function rememberRoomSuggestionAttributions(
|
|
44
|
+
suggestions: readonly Pick<RoomSuggestion, "room" | "suggestionId">[],
|
|
45
|
+
hook: string,
|
|
46
|
+
now = Date.now(),
|
|
47
|
+
): void {
|
|
48
|
+
for (const suggestion of suggestions) {
|
|
49
|
+
rememberRoomSuggestionAttribution(suggestion, hook, now);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function consumeRoomSuggestionAttribution(
|
|
54
|
+
room: string,
|
|
55
|
+
now = Date.now(),
|
|
56
|
+
): RoomSuggestionAttribution | undefined {
|
|
57
|
+
const roomKey = normalizeRoomKey(room);
|
|
58
|
+
if (!roomKey) return undefined;
|
|
59
|
+
const attribution = roomSuggestionAttributions.get(roomKey);
|
|
60
|
+
if (!attribution) return undefined;
|
|
61
|
+
roomSuggestionAttributions.delete(roomKey);
|
|
62
|
+
if (attribution.expiresAt <= now) return undefined;
|
|
63
|
+
return {
|
|
64
|
+
suggestionId: attribution.suggestionId,
|
|
65
|
+
hook: attribution.hook,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function resetRoomSuggestionAttributionForTest(): void {
|
|
70
|
+
roomSuggestionAttributions.clear();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function pruneExpiredRoomSuggestionAttributions(now: number): void {
|
|
74
|
+
for (const [room, attribution] of roomSuggestionAttributions) {
|
|
75
|
+
if (attribution.expiresAt <= now) {
|
|
76
|
+
roomSuggestionAttributions.delete(room);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function normalizeRoomKey(room: string): string {
|
|
82
|
+
return room.trim().replace(/^#/, "").toLowerCase();
|
|
83
|
+
}
|
package/src/topic-uri.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* om://topic URIs: permanent links into a channel's topics.
|
|
5
|
+
*
|
|
6
|
+
* The id is registry-backed and never changes, so a captured link
|
|
7
|
+
* (`om://topic/<topicId>`) keeps resolving after the topic is renamed or
|
|
8
|
+
* merged into another topic. Clients that understand the scheme render it as
|
|
9
|
+
* a pill and can annotate it with the topic's live name from a local cache;
|
|
10
|
+
* clients that don't still show a usable, resolvable reference.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface TopicUriRef {
|
|
14
|
+
topicId: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const TOPIC_URI_RE = /om:\/\/topic\/([A-Za-z0-9_-]{6,64})/g;
|
|
18
|
+
|
|
19
|
+
export function makeTopicUri(topicId: string): string {
|
|
20
|
+
return `om://topic/${topicId}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function parseTopicUris(text: string): TopicUriRef[] {
|
|
24
|
+
const refs: TopicUriRef[] = [];
|
|
25
|
+
for (const match of text.matchAll(TOPIC_URI_RE)) {
|
|
26
|
+
const topicId = match[1];
|
|
27
|
+
if (!topicId) continue;
|
|
28
|
+
refs.push({ topicId });
|
|
29
|
+
}
|
|
30
|
+
return refs;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface TopicUriRenderContext {
|
|
34
|
+
/** Resolve a topicId to its live name, when known. */
|
|
35
|
+
lookup?: ((topicId: string) => string | undefined) | undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Process-wide lookup used by the tape renderers, mirroring
|
|
40
|
+
* `setDocUriLookup` in ./doc-uri. Renderers work unregistered too: pills
|
|
41
|
+
* just lose the name annotation and fall back to a truncated id.
|
|
42
|
+
*/
|
|
43
|
+
let activeLookup: TopicUriRenderContext["lookup"];
|
|
44
|
+
|
|
45
|
+
export function setTopicUriLookup(lookup: TopicUriRenderContext["lookup"]): void {
|
|
46
|
+
activeLookup = lookup;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function decorateTopicUrisLive(text: string): string {
|
|
50
|
+
if (!text.includes("om://topic/")) return text;
|
|
51
|
+
return decorateTopicUris(text, { lookup: activeLookup });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Replace om://topic URIs with a rendered pill: the topic's live name when a
|
|
56
|
+
* cache lookup resolves it, otherwise a compact id reference that is still
|
|
57
|
+
* visibly a topic link.
|
|
58
|
+
*/
|
|
59
|
+
export function decorateTopicUris(text: string, context: TopicUriRenderContext = {}): string {
|
|
60
|
+
return text.replace(TOPIC_URI_RE, (_raw, topicId: string) => {
|
|
61
|
+
const name = context.lookup?.(topicId);
|
|
62
|
+
return name ? pc.cyan(`⌗ ${name}`) : pc.cyan(`⌗ topic:${topicId.slice(0, 8)}`);
|
|
63
|
+
});
|
|
64
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
RoomAccessMode,
|
|
3
|
+
RoomAttachment,
|
|
4
|
+
RoomAuditEntry,
|
|
5
|
+
RoomAuditLogPayload,
|
|
6
|
+
RoomAuditLogRequestPayload,
|
|
7
|
+
RoomAuthSuccessPayload,
|
|
8
|
+
RoomBackfillPayload,
|
|
9
|
+
RoomClientMessage,
|
|
10
|
+
RoomCreatedPayload,
|
|
11
|
+
RoomCreatePayload,
|
|
12
|
+
RoomDmHistoryPayload,
|
|
13
|
+
RoomDmHistoryResultPayload,
|
|
14
|
+
RoomDmInboxUpdatedPayload,
|
|
15
|
+
RoomDmMessage,
|
|
16
|
+
RoomDmMessagePayload,
|
|
17
|
+
RoomDmOpenedPayload,
|
|
18
|
+
RoomDmOpenPayload,
|
|
19
|
+
RoomDmParticipant,
|
|
20
|
+
RoomDmReadPayload,
|
|
21
|
+
RoomDmSendPayload,
|
|
22
|
+
RoomEntryUpdatedPayload,
|
|
23
|
+
RoomErrorPayload,
|
|
24
|
+
RoomFriendPresence,
|
|
25
|
+
RoomInvitedPayload,
|
|
26
|
+
RoomInvitePayload,
|
|
27
|
+
RoomLedgerEntry,
|
|
28
|
+
RoomMember,
|
|
29
|
+
RoomMemberRemovedPayload,
|
|
30
|
+
RoomMemberRoleChangedPayload,
|
|
31
|
+
RoomMemberTier,
|
|
32
|
+
RoomMessagePayload,
|
|
33
|
+
RoomMeta,
|
|
34
|
+
RoomModerateMemberPayload,
|
|
35
|
+
RoomMutedPayload,
|
|
36
|
+
RoomPinsPayload,
|
|
37
|
+
RoomPresenceDeltaPayload,
|
|
38
|
+
RoomPresencePayload,
|
|
39
|
+
RoomReactorsPayload,
|
|
40
|
+
RoomReadStatePayload,
|
|
41
|
+
RoomRemoveMemberPayload,
|
|
42
|
+
RoomReportedPayload,
|
|
43
|
+
RoomReportPayload,
|
|
44
|
+
RoomResyncPayload,
|
|
45
|
+
RoomRole,
|
|
46
|
+
RoomRoleChangePayload,
|
|
47
|
+
RoomSearchPayload,
|
|
48
|
+
RoomSearchResultsPayload,
|
|
49
|
+
RoomSearchScope,
|
|
50
|
+
RoomServerMessage,
|
|
51
|
+
RoomServerMessageOf,
|
|
52
|
+
RoomSetReadOnlyPayload,
|
|
53
|
+
RoomSpace,
|
|
54
|
+
RoomSpaceJoinedPayload,
|
|
55
|
+
RoomSpacesPayload,
|
|
56
|
+
RoomThreadPayload,
|
|
57
|
+
RoomTopic,
|
|
58
|
+
RoomTopicAttention,
|
|
59
|
+
RoomTopicBucketState,
|
|
60
|
+
RoomTopicCreatePayload,
|
|
61
|
+
RoomTopicListPayload,
|
|
62
|
+
RoomTopicMergePayload,
|
|
63
|
+
RoomTopicMovePayload,
|
|
64
|
+
RoomTopicRenamePayload,
|
|
65
|
+
RoomTopicResolvePayload,
|
|
66
|
+
RoomTopicResultPayload,
|
|
67
|
+
RoomTopicSetAttentionPayload,
|
|
68
|
+
RoomTopicsPolicy,
|
|
69
|
+
RoomTopicsResultPayload,
|
|
70
|
+
RoomTopicUpdatedPayload,
|
|
71
|
+
RoomTypingPayload,
|
|
72
|
+
RoomUserStatus,
|
|
73
|
+
RoomWhoPayload,
|
|
74
|
+
} from "./shared/rooms-protocol.js";
|
|
75
|
+
export {
|
|
76
|
+
applyRoomPresenceDelta,
|
|
77
|
+
isSystemRoomLedgerEntryType,
|
|
78
|
+
ROOM_PRESENCE_SAMPLE_LIMIT,
|
|
79
|
+
RoomClientMessageType,
|
|
80
|
+
RoomLedgerEntryType,
|
|
81
|
+
RoomServerMessageType,
|
|
82
|
+
SUPPORTED_ROOM_PROTOCOL_VERSION,
|
|
83
|
+
} from "./shared/rooms-protocol.js";
|
package/src/version.ts
ADDED