@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,338 @@
|
|
|
1
|
+
import type { RoomLedgerSecretPayload } from "./shared/rooms-protocol.js";
|
|
2
|
+
export interface RoomSocialConfig {
|
|
3
|
+
apiUrl: string;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
}
|
|
6
|
+
export type RoomSuggestionSource = "init" | "alert_create" | "market_lookup" | "status" | "digest";
|
|
7
|
+
export interface RoomSuggestionIntent {
|
|
8
|
+
symbols?: string[];
|
|
9
|
+
entities?: string[];
|
|
10
|
+
categories?: string[];
|
|
11
|
+
roomHints?: string[];
|
|
12
|
+
reason?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface RoomSuggestionRequest {
|
|
15
|
+
source: RoomSuggestionSource;
|
|
16
|
+
intent?: RoomSuggestionIntent;
|
|
17
|
+
limit?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface RoomSuggestion {
|
|
20
|
+
room: string;
|
|
21
|
+
title: string;
|
|
22
|
+
proof: {
|
|
23
|
+
online: number;
|
|
24
|
+
heat: "surging" | "active_now";
|
|
25
|
+
latestActivity?: string;
|
|
26
|
+
followingOnline?: number;
|
|
27
|
+
followingHandles?: string[];
|
|
28
|
+
friendsOnline?: number;
|
|
29
|
+
friendHandles?: string[];
|
|
30
|
+
};
|
|
31
|
+
copy: string;
|
|
32
|
+
suggestionId: string;
|
|
33
|
+
}
|
|
34
|
+
export interface RoomFollow {
|
|
35
|
+
userId: string;
|
|
36
|
+
profileUrl?: string | undefined;
|
|
37
|
+
}
|
|
38
|
+
export interface RoomFollowTarget {
|
|
39
|
+
userId: string;
|
|
40
|
+
username: string;
|
|
41
|
+
}
|
|
42
|
+
export interface RoomFollowResult {
|
|
43
|
+
target: RoomFollowTarget;
|
|
44
|
+
isNewFollow?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface RoomAttachment {
|
|
47
|
+
/** Object key under room-attachments/; clients sign it at render time. */
|
|
48
|
+
key: string;
|
|
49
|
+
name: string;
|
|
50
|
+
size: number;
|
|
51
|
+
mime: string;
|
|
52
|
+
scanStatus?: "pending" | "clean" | "held" | undefined;
|
|
53
|
+
}
|
|
54
|
+
export interface RoomAttachmentUpload {
|
|
55
|
+
/** The stored attachment plus its first signed URL, for immediate render. */
|
|
56
|
+
attachment: RoomAttachment & {
|
|
57
|
+
url: string;
|
|
58
|
+
expiresAt: string | null;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export interface RoomPublicProfile {
|
|
62
|
+
userId: string;
|
|
63
|
+
displayName?: string | undefined;
|
|
64
|
+
username?: string | undefined;
|
|
65
|
+
handle?: string | undefined;
|
|
66
|
+
profileDescription?: string | undefined;
|
|
67
|
+
profileUrl?: string | undefined;
|
|
68
|
+
followerCount?: number | undefined;
|
|
69
|
+
memberSince?: string | number | null | undefined;
|
|
70
|
+
}
|
|
71
|
+
export interface RoomFriend {
|
|
72
|
+
friendshipId: string;
|
|
73
|
+
userId: string;
|
|
74
|
+
username: string;
|
|
75
|
+
acceptedAt: string | number;
|
|
76
|
+
lastMessageAt?: string | number;
|
|
77
|
+
}
|
|
78
|
+
export interface RoomFriendRequest {
|
|
79
|
+
friendshipId: string;
|
|
80
|
+
userId: string;
|
|
81
|
+
username: string;
|
|
82
|
+
createdAt: string | number;
|
|
83
|
+
}
|
|
84
|
+
export interface RoomFriendRequests {
|
|
85
|
+
sent: RoomFriendRequest[];
|
|
86
|
+
received: RoomFriendRequest[];
|
|
87
|
+
}
|
|
88
|
+
export interface RoomDmParticipant {
|
|
89
|
+
userId: string;
|
|
90
|
+
username: string;
|
|
91
|
+
}
|
|
92
|
+
export interface RoomDmConversation {
|
|
93
|
+
conversationId: string;
|
|
94
|
+
participant: RoomDmParticipant;
|
|
95
|
+
unreadCount: number;
|
|
96
|
+
lastMessage?: RoomDmMessage;
|
|
97
|
+
updatedAt?: string | number;
|
|
98
|
+
}
|
|
99
|
+
export interface RoomDmMessage {
|
|
100
|
+
id?: string;
|
|
101
|
+
_id?: string;
|
|
102
|
+
messageId?: string;
|
|
103
|
+
conversationId?: string;
|
|
104
|
+
senderId?: string;
|
|
105
|
+
userId?: string;
|
|
106
|
+
senderUsername?: string;
|
|
107
|
+
username?: string;
|
|
108
|
+
content: string;
|
|
109
|
+
type?: string;
|
|
110
|
+
timestamp?: string | number;
|
|
111
|
+
createdAt?: string | number;
|
|
112
|
+
/** Sealed DM: content is '' and the opaque envelope rides here. */
|
|
113
|
+
secret?: RoomLedgerSecretPayload | null;
|
|
114
|
+
metadata?: Record<string, unknown>;
|
|
115
|
+
}
|
|
116
|
+
export interface RoomDmHistory {
|
|
117
|
+
conversationId: string;
|
|
118
|
+
participant: RoomDmParticipant;
|
|
119
|
+
messages: RoomDmMessage[];
|
|
120
|
+
hasMore: boolean;
|
|
121
|
+
cursor?: number;
|
|
122
|
+
}
|
|
123
|
+
export interface RoomDmUnreadConversation extends RoomDmHistory {
|
|
124
|
+
unreadCount: number;
|
|
125
|
+
}
|
|
126
|
+
export interface RoomDmSendResult {
|
|
127
|
+
conversationId: string;
|
|
128
|
+
participant: RoomDmParticipant;
|
|
129
|
+
message: RoomDmMessage;
|
|
130
|
+
}
|
|
131
|
+
export interface RoomReplyInboxItem {
|
|
132
|
+
id?: string;
|
|
133
|
+
roomId: string;
|
|
134
|
+
replySeq: number;
|
|
135
|
+
parentSeq: number;
|
|
136
|
+
senderId?: string;
|
|
137
|
+
senderHandle: string;
|
|
138
|
+
parentPreview: string;
|
|
139
|
+
replyPreview: string;
|
|
140
|
+
readAt?: string | number | null;
|
|
141
|
+
createdAt?: string | number;
|
|
142
|
+
updatedAt?: string | number;
|
|
143
|
+
}
|
|
144
|
+
export interface RoomRepliesResult {
|
|
145
|
+
replies: RoomReplyInboxItem[];
|
|
146
|
+
unreadCount: number;
|
|
147
|
+
}
|
|
148
|
+
export interface RoomRepliesReadResult {
|
|
149
|
+
markedRead: number;
|
|
150
|
+
}
|
|
151
|
+
/** A living markdown doc scoped to a space (head state; content on demand). */
|
|
152
|
+
export interface RoomDoc {
|
|
153
|
+
docId: string;
|
|
154
|
+
spaceId: string;
|
|
155
|
+
path: string;
|
|
156
|
+
title: string;
|
|
157
|
+
headRev: number;
|
|
158
|
+
headSha: string;
|
|
159
|
+
headNote: string;
|
|
160
|
+
headAuthorId: string;
|
|
161
|
+
headAuthorHandle: string;
|
|
162
|
+
headIsAgent: boolean;
|
|
163
|
+
headAt: string;
|
|
164
|
+
status: "active" | "archived";
|
|
165
|
+
aclMode: "space" | "owner";
|
|
166
|
+
restrictedToRoomId: string | null;
|
|
167
|
+
discussionRoomId: string | null;
|
|
168
|
+
createdByUserId: string;
|
|
169
|
+
createdByHandle: string;
|
|
170
|
+
sizeBytes: number;
|
|
171
|
+
spaceDocSeq: number;
|
|
172
|
+
createdAt?: string;
|
|
173
|
+
updatedAt?: string;
|
|
174
|
+
content?: string;
|
|
175
|
+
}
|
|
176
|
+
export interface RoomDocProvenance {
|
|
177
|
+
roomId?: string | undefined;
|
|
178
|
+
seqFrom?: number | undefined;
|
|
179
|
+
seqTo?: number | undefined;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Which surface issued a doc write: the rooms GUI editor, an agent doc verb,
|
|
183
|
+
* the local disk mirror (library sync), or the TUI/CLI flows. Self-declared
|
|
184
|
+
* attribution for the history view (same trust model as `asAgent`), never
|
|
185
|
+
* enforcement.
|
|
186
|
+
*/
|
|
187
|
+
export type RoomDocRevisionSource = "gui" | "agent" | "mirror" | "cli";
|
|
188
|
+
/** Optional attribution rider accepted by every doc write. */
|
|
189
|
+
export interface RoomDocAttribution {
|
|
190
|
+
/** Phase 1 agent credential id (`agcr_*`) when an agent authored the write. */
|
|
191
|
+
credentialId?: string | undefined;
|
|
192
|
+
/** Groups every revision one agent run produced. */
|
|
193
|
+
runId?: string | undefined;
|
|
194
|
+
/** Surface that issued the write. */
|
|
195
|
+
source?: RoomDocRevisionSource | undefined;
|
|
196
|
+
}
|
|
197
|
+
export interface RoomDocRevision {
|
|
198
|
+
docId: string;
|
|
199
|
+
rev: number;
|
|
200
|
+
op: string;
|
|
201
|
+
authorUserId: string;
|
|
202
|
+
authorHandle: string;
|
|
203
|
+
isAgent: boolean;
|
|
204
|
+
baseRev: number;
|
|
205
|
+
sha: string;
|
|
206
|
+
sizeBytes: number;
|
|
207
|
+
note: string;
|
|
208
|
+
noteSource: "human" | "agent" | "auto";
|
|
209
|
+
/** Attribution rider (null on pre-attribution revisions and older servers). */
|
|
210
|
+
credentialId?: string | null;
|
|
211
|
+
runId?: string | null;
|
|
212
|
+
source?: RoomDocRevisionSource | null;
|
|
213
|
+
provenance: RoomDocProvenance | null;
|
|
214
|
+
pathAfter: string | null;
|
|
215
|
+
revertOf: number | null;
|
|
216
|
+
recoveryPlanId?: string | null;
|
|
217
|
+
restorePoint?: string | null;
|
|
218
|
+
statusAfter?: "active" | "archived" | null;
|
|
219
|
+
at: string;
|
|
220
|
+
content?: string;
|
|
221
|
+
diff?: string;
|
|
222
|
+
diffOmitted?: boolean;
|
|
223
|
+
}
|
|
224
|
+
export type RoomSpaceRestoreActionKind = "content" | "archive" | "restore" | "content_and_status" | "archive_created_after";
|
|
225
|
+
export interface RoomSpaceRestoreAction {
|
|
226
|
+
docId: string;
|
|
227
|
+
path: string;
|
|
228
|
+
kind: RoomSpaceRestoreActionKind;
|
|
229
|
+
currentRev: number;
|
|
230
|
+
targetRev: number | null;
|
|
231
|
+
currentStatus: "active" | "archived";
|
|
232
|
+
targetStatus: "active" | "archived";
|
|
233
|
+
currentAuthorUserId: string;
|
|
234
|
+
currentAuthorHandle: string;
|
|
235
|
+
currentAt: string;
|
|
236
|
+
targetAt: string | null;
|
|
237
|
+
}
|
|
238
|
+
export interface RoomSpaceRestoreSummary {
|
|
239
|
+
totalDocs: number;
|
|
240
|
+
totalActions: number;
|
|
241
|
+
contentRestores: number;
|
|
242
|
+
archives: number;
|
|
243
|
+
restores: number;
|
|
244
|
+
pathConflicts: number;
|
|
245
|
+
unchanged: number;
|
|
246
|
+
}
|
|
247
|
+
export interface RoomSpaceRestorePlan {
|
|
248
|
+
planId: string;
|
|
249
|
+
spaceId: string;
|
|
250
|
+
restoreAt: string;
|
|
251
|
+
baseCursor: number;
|
|
252
|
+
reason: string;
|
|
253
|
+
requestedByHandle: string;
|
|
254
|
+
status: "ready" | "applying" | "applied" | "failed" | "stale";
|
|
255
|
+
scope: "content_and_lifecycle";
|
|
256
|
+
scopeNote: string;
|
|
257
|
+
actions: RoomSpaceRestoreAction[];
|
|
258
|
+
summary: RoomSpaceRestoreSummary;
|
|
259
|
+
appliedCount: number;
|
|
260
|
+
expiresAt: string;
|
|
261
|
+
appliedAt: string | null;
|
|
262
|
+
resultingCursor: number | null;
|
|
263
|
+
error: string | null;
|
|
264
|
+
}
|
|
265
|
+
export interface RoomSpaceRestoreReceipt {
|
|
266
|
+
planId: string;
|
|
267
|
+
spaceId: string;
|
|
268
|
+
restoreAt: string;
|
|
269
|
+
reason: string;
|
|
270
|
+
status: "applied";
|
|
271
|
+
appliedActions: number;
|
|
272
|
+
resultingCursor: number;
|
|
273
|
+
appliedAt: string;
|
|
274
|
+
summary: RoomSpaceRestoreSummary;
|
|
275
|
+
}
|
|
276
|
+
/** Resumable per-space change feed page. `latestSeq` is the space's current
|
|
277
|
+
* head cursor: `cursor === latestSeq` means the caller is fully in sync. */
|
|
278
|
+
export interface RoomDocChanges {
|
|
279
|
+
changes: RoomDoc[];
|
|
280
|
+
cursor: number;
|
|
281
|
+
latestSeq: number;
|
|
282
|
+
}
|
|
283
|
+
export interface RoomDocConflictHead {
|
|
284
|
+
rev: number;
|
|
285
|
+
sha: string;
|
|
286
|
+
content: string;
|
|
287
|
+
note: string;
|
|
288
|
+
authorHandle: string;
|
|
289
|
+
isAgent: boolean;
|
|
290
|
+
at: string;
|
|
291
|
+
}
|
|
292
|
+
export interface RoomDocWriteResult {
|
|
293
|
+
doc: RoomDoc;
|
|
294
|
+
rev: number;
|
|
295
|
+
unchanged: boolean;
|
|
296
|
+
}
|
|
297
|
+
/** One diverging chunk from a three-way merge (line arrays, no separators). */
|
|
298
|
+
export interface RoomDocMergeRegion {
|
|
299
|
+
base: string[];
|
|
300
|
+
mine: string[];
|
|
301
|
+
theirs: string[];
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Outcome of a selective run revert. A merge conflict is OUTCOME data, not
|
|
305
|
+
* an error: nothing was written, and the payload carries the three full
|
|
306
|
+
* contents (base = the run's final content, mine = the current head,
|
|
307
|
+
* theirs = the pre-run content) plus the diverging regions so the reconcile
|
|
308
|
+
* surface can mediate. `headRev` is the fence a mediated resolution
|
|
309
|
+
* publishes against.
|
|
310
|
+
*/
|
|
311
|
+
export type RoomDocRunRevertResult = {
|
|
312
|
+
status: "reverted";
|
|
313
|
+
doc: RoomDoc;
|
|
314
|
+
rev: number;
|
|
315
|
+
unchanged: boolean;
|
|
316
|
+
} | {
|
|
317
|
+
status: "conflict";
|
|
318
|
+
headRev: number;
|
|
319
|
+
runId: string;
|
|
320
|
+
base: string;
|
|
321
|
+
mine: string;
|
|
322
|
+
theirs: string;
|
|
323
|
+
regions: RoomDocMergeRegion[];
|
|
324
|
+
};
|
|
325
|
+
/**
|
|
326
|
+
* An editing-presence lease: the HINT that a user currently holds a dirty
|
|
327
|
+
* working copy of a doc on some device. Renewed by heartbeat, dropped on
|
|
328
|
+
* publish/discard, TTL-expired otherwise. Never a lock, never content;
|
|
329
|
+
* `isAgent` lets UIs class agent editors per the phase 3 presence rules.
|
|
330
|
+
*/
|
|
331
|
+
export interface RoomDocEditingLease {
|
|
332
|
+
docId: string;
|
|
333
|
+
spaceId: string;
|
|
334
|
+
userId: string;
|
|
335
|
+
handle: string;
|
|
336
|
+
isAgent: boolean;
|
|
337
|
+
expiresAt: string;
|
|
338
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RoomSuggestion } from "./social-types.js";
|
|
2
|
+
export interface RoomSuggestionAttribution {
|
|
3
|
+
suggestionId: string;
|
|
4
|
+
hook: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const ROOM_SUGGESTION_ATTRIBUTION_TTL_MS: number;
|
|
7
|
+
export declare function rememberRoomSuggestionAttribution(suggestion: Pick<RoomSuggestion, "room" | "suggestionId">, hook: string, now?: number): void;
|
|
8
|
+
export declare function rememberRoomSuggestionAttributions(suggestions: readonly Pick<RoomSuggestion, "room" | "suggestionId">[], hook: string, now?: number): void;
|
|
9
|
+
export declare function consumeRoomSuggestionAttribution(room: string, now?: number): RoomSuggestionAttribution | undefined;
|
|
10
|
+
export declare function resetRoomSuggestionAttributionForTest(): void;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const ROOM_SUGGESTION_ATTRIBUTION_TTL_MS = 15 * 60 * 1000;
|
|
2
|
+
const MAX_ROOM_SUGGESTION_ATTRIBUTIONS = 100;
|
|
3
|
+
const roomSuggestionAttributions = new Map();
|
|
4
|
+
export function rememberRoomSuggestionAttribution(suggestion, hook, now = Date.now()) {
|
|
5
|
+
const room = normalizeRoomKey(suggestion.room);
|
|
6
|
+
const suggestionId = suggestion.suggestionId.trim();
|
|
7
|
+
const normalizedHook = hook.trim();
|
|
8
|
+
if (!room || !suggestionId || !normalizedHook)
|
|
9
|
+
return;
|
|
10
|
+
pruneExpiredRoomSuggestionAttributions(now);
|
|
11
|
+
while (roomSuggestionAttributions.size >= MAX_ROOM_SUGGESTION_ATTRIBUTIONS &&
|
|
12
|
+
!roomSuggestionAttributions.has(room)) {
|
|
13
|
+
const oldest = roomSuggestionAttributions.keys().next().value;
|
|
14
|
+
if (!oldest)
|
|
15
|
+
break;
|
|
16
|
+
roomSuggestionAttributions.delete(oldest);
|
|
17
|
+
}
|
|
18
|
+
roomSuggestionAttributions.set(room, {
|
|
19
|
+
suggestionId,
|
|
20
|
+
hook: normalizedHook,
|
|
21
|
+
expiresAt: now + ROOM_SUGGESTION_ATTRIBUTION_TTL_MS,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export function rememberRoomSuggestionAttributions(suggestions, hook, now = Date.now()) {
|
|
25
|
+
for (const suggestion of suggestions) {
|
|
26
|
+
rememberRoomSuggestionAttribution(suggestion, hook, now);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function consumeRoomSuggestionAttribution(room, now = Date.now()) {
|
|
30
|
+
const roomKey = normalizeRoomKey(room);
|
|
31
|
+
if (!roomKey)
|
|
32
|
+
return undefined;
|
|
33
|
+
const attribution = roomSuggestionAttributions.get(roomKey);
|
|
34
|
+
if (!attribution)
|
|
35
|
+
return undefined;
|
|
36
|
+
roomSuggestionAttributions.delete(roomKey);
|
|
37
|
+
if (attribution.expiresAt <= now)
|
|
38
|
+
return undefined;
|
|
39
|
+
return {
|
|
40
|
+
suggestionId: attribution.suggestionId,
|
|
41
|
+
hook: attribution.hook,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function resetRoomSuggestionAttributionForTest() {
|
|
45
|
+
roomSuggestionAttributions.clear();
|
|
46
|
+
}
|
|
47
|
+
function pruneExpiredRoomSuggestionAttributions(now) {
|
|
48
|
+
for (const [room, attribution] of roomSuggestionAttributions) {
|
|
49
|
+
if (attribution.expiresAt <= now) {
|
|
50
|
+
roomSuggestionAttributions.delete(room);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function normalizeRoomKey(room) {
|
|
55
|
+
return room.trim().replace(/^#/, "").toLowerCase();
|
|
56
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* om://topic URIs: permanent links into a channel's topics.
|
|
3
|
+
*
|
|
4
|
+
* The id is registry-backed and never changes, so a captured link
|
|
5
|
+
* (`om://topic/<topicId>`) keeps resolving after the topic is renamed or
|
|
6
|
+
* merged into another topic. Clients that understand the scheme render it as
|
|
7
|
+
* a pill and can annotate it with the topic's live name from a local cache;
|
|
8
|
+
* clients that don't still show a usable, resolvable reference.
|
|
9
|
+
*/
|
|
10
|
+
export interface TopicUriRef {
|
|
11
|
+
topicId: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function makeTopicUri(topicId: string): string;
|
|
14
|
+
export declare function parseTopicUris(text: string): TopicUriRef[];
|
|
15
|
+
export interface TopicUriRenderContext {
|
|
16
|
+
/** Resolve a topicId to its live name, when known. */
|
|
17
|
+
lookup?: ((topicId: string) => string | undefined) | undefined;
|
|
18
|
+
}
|
|
19
|
+
export declare function setTopicUriLookup(lookup: TopicUriRenderContext["lookup"]): void;
|
|
20
|
+
export declare function decorateTopicUrisLive(text: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Replace om://topic URIs with a rendered pill: the topic's live name when a
|
|
23
|
+
* cache lookup resolves it, otherwise a compact id reference that is still
|
|
24
|
+
* visibly a topic link.
|
|
25
|
+
*/
|
|
26
|
+
export declare function decorateTopicUris(text: string, context?: TopicUriRenderContext): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
const TOPIC_URI_RE = /om:\/\/topic\/([A-Za-z0-9_-]{6,64})/g;
|
|
3
|
+
export function makeTopicUri(topicId) {
|
|
4
|
+
return `om://topic/${topicId}`;
|
|
5
|
+
}
|
|
6
|
+
export function parseTopicUris(text) {
|
|
7
|
+
const refs = [];
|
|
8
|
+
for (const match of text.matchAll(TOPIC_URI_RE)) {
|
|
9
|
+
const topicId = match[1];
|
|
10
|
+
if (!topicId)
|
|
11
|
+
continue;
|
|
12
|
+
refs.push({ topicId });
|
|
13
|
+
}
|
|
14
|
+
return refs;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Process-wide lookup used by the tape renderers, mirroring
|
|
18
|
+
* `setDocUriLookup` in ./doc-uri. Renderers work unregistered too: pills
|
|
19
|
+
* just lose the name annotation and fall back to a truncated id.
|
|
20
|
+
*/
|
|
21
|
+
let activeLookup;
|
|
22
|
+
export function setTopicUriLookup(lookup) {
|
|
23
|
+
activeLookup = lookup;
|
|
24
|
+
}
|
|
25
|
+
export function decorateTopicUrisLive(text) {
|
|
26
|
+
if (!text.includes("om://topic/"))
|
|
27
|
+
return text;
|
|
28
|
+
return decorateTopicUris(text, { lookup: activeLookup });
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Replace om://topic URIs with a rendered pill: the topic's live name when a
|
|
32
|
+
* cache lookup resolves it, otherwise a compact id reference that is still
|
|
33
|
+
* visibly a topic link.
|
|
34
|
+
*/
|
|
35
|
+
export function decorateTopicUris(text, context = {}) {
|
|
36
|
+
return text.replace(TOPIC_URI_RE, (_raw, topicId) => {
|
|
37
|
+
const name = context.lookup?.(topicId);
|
|
38
|
+
return name ? pc.cyan(`⌗ ${name}`) : pc.cyan(`⌗ topic:${topicId.slice(0, 8)}`);
|
|
39
|
+
});
|
|
40
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export type { RoomAccessMode, RoomAttachment, RoomAuditEntry, RoomAuditLogPayload, RoomAuditLogRequestPayload, RoomAuthSuccessPayload, RoomBackfillPayload, RoomClientMessage, RoomCreatedPayload, RoomCreatePayload, RoomDmHistoryPayload, RoomDmHistoryResultPayload, RoomDmInboxUpdatedPayload, RoomDmMessage, RoomDmMessagePayload, RoomDmOpenedPayload, RoomDmOpenPayload, RoomDmParticipant, RoomDmReadPayload, RoomDmSendPayload, RoomEntryUpdatedPayload, RoomErrorPayload, RoomFriendPresence, RoomInvitedPayload, RoomInvitePayload, RoomLedgerEntry, RoomMember, RoomMemberRemovedPayload, RoomMemberRoleChangedPayload, RoomMemberTier, RoomMessagePayload, RoomMeta, RoomModerateMemberPayload, RoomMutedPayload, RoomPinsPayload, RoomPresenceDeltaPayload, RoomPresencePayload, RoomReactorsPayload, RoomReadStatePayload, RoomRemoveMemberPayload, RoomReportedPayload, RoomReportPayload, RoomResyncPayload, RoomRole, RoomRoleChangePayload, RoomSearchPayload, RoomSearchResultsPayload, RoomSearchScope, RoomServerMessage, RoomServerMessageOf, RoomSetReadOnlyPayload, RoomSpace, RoomSpaceJoinedPayload, RoomSpacesPayload, RoomThreadPayload, RoomTopic, RoomTopicAttention, RoomTopicBucketState, RoomTopicCreatePayload, RoomTopicListPayload, RoomTopicMergePayload, RoomTopicMovePayload, RoomTopicRenamePayload, RoomTopicResolvePayload, RoomTopicResultPayload, RoomTopicSetAttentionPayload, RoomTopicsPolicy, RoomTopicsResultPayload, RoomTopicUpdatedPayload, RoomTypingPayload, RoomUserStatus, RoomWhoPayload, } from "./shared/rooms-protocol.js";
|
|
2
|
+
export { applyRoomPresenceDelta, isSystemRoomLedgerEntryType, ROOM_PRESENCE_SAMPLE_LIMIT, RoomClientMessageType, RoomLedgerEntryType, RoomServerMessageType, SUPPORTED_ROOM_PROTOCOL_VERSION, } from "./shared/rooms-protocol.js";
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { applyRoomPresenceDelta, isSystemRoomLedgerEntryType, ROOM_PRESENCE_SAMPLE_LIMIT, RoomClientMessageType, RoomLedgerEntryType, RoomServerMessageType, SUPPORTED_ROOM_PROTOCOL_VERSION, } from "./shared/rooms-protocol.js";
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { type RoomAuthSuccessPayload, type RoomClientMessage, type RoomErrorPayload, type RoomServerMessage, type RoomServerMessageOf, RoomServerMessageType } from "./types.js";
|
|
2
|
+
export declare class RoomProtocolError extends Error {
|
|
3
|
+
readonly name = "RoomProtocolError";
|
|
4
|
+
readonly code: string;
|
|
5
|
+
readonly details?: unknown;
|
|
6
|
+
readonly requestId?: string;
|
|
7
|
+
constructor(payload: RoomErrorPayload);
|
|
8
|
+
}
|
|
9
|
+
export declare class RoomTransportError extends Error {
|
|
10
|
+
readonly name = "RoomTransportError";
|
|
11
|
+
}
|
|
12
|
+
export type RoomWebSocketCtor = new (url: string) => WebSocket;
|
|
13
|
+
export interface RoomWsClientOptions {
|
|
14
|
+
url: string;
|
|
15
|
+
apiKey: string;
|
|
16
|
+
/** Refreshes a locally managed credential after an authentication refusal. */
|
|
17
|
+
refreshAuthToken?: () => Promise<string>;
|
|
18
|
+
webSocketCtor?: RoomWebSocketCtor;
|
|
19
|
+
timeoutMs?: number;
|
|
20
|
+
/** Enable bounded exponential-backoff reconnection on an unexpected drop. */
|
|
21
|
+
reconnect?: boolean;
|
|
22
|
+
/** Base backoff in ms (default 500). */
|
|
23
|
+
reconnectBaseMs?: number;
|
|
24
|
+
/** Max backoff in ms (default 15_000). */
|
|
25
|
+
reconnectMaxMs?: number;
|
|
26
|
+
/** Flat 0..N ms herd jitter added to every reconnect delay (default 5_000).
|
|
27
|
+
* A relay restart drops every client at once; the exponential backoff's
|
|
28
|
+
* first window is only ~250-500ms, so without this the whole fleet
|
|
29
|
+
* re-AUTHENTICATEs in the same instant. */
|
|
30
|
+
reconnectHerdJitterMs?: number;
|
|
31
|
+
/** Give up after this many consecutive failures (default: never; live sessions reconnect indefinitely). */
|
|
32
|
+
reconnectMaxAttempts?: number;
|
|
33
|
+
/** Sidekick identity assertion: this session presents itself as the named
|
|
34
|
+
* operator-owned agent (AUTHENTICATE carries isAgent plus the display
|
|
35
|
+
* username, additive fields old relays ignore). Ghost and plain user
|
|
36
|
+
* sessions omit it entirely, keeping their first frame byte-shaped like
|
|
37
|
+
* today's. Display only; safety keys on the daemon credential. */
|
|
38
|
+
agentIdentity?: {
|
|
39
|
+
name: string;
|
|
40
|
+
};
|
|
41
|
+
onProtocolMismatch?: (info: {
|
|
42
|
+
supported: number;
|
|
43
|
+
server: number;
|
|
44
|
+
}) => void;
|
|
45
|
+
}
|
|
46
|
+
type RoomListener = (msg: RoomServerMessage) => void;
|
|
47
|
+
/** Notified when the socket drops and when it comes back after a reconnect. */
|
|
48
|
+
export type RoomConnectionListener = (state: "reconnecting" | "reconnected" | "failed", detail?: {
|
|
49
|
+
attempt?: number;
|
|
50
|
+
error?: string;
|
|
51
|
+
}) => void;
|
|
52
|
+
export declare class RoomWsClient {
|
|
53
|
+
private readonly url;
|
|
54
|
+
private apiKey;
|
|
55
|
+
private readonly agentIdentity;
|
|
56
|
+
private readonly webSocketCtor;
|
|
57
|
+
private readonly timeoutMs;
|
|
58
|
+
private ws;
|
|
59
|
+
private auth;
|
|
60
|
+
private serverProtocolVersionValue;
|
|
61
|
+
private listeners;
|
|
62
|
+
private connectionListeners;
|
|
63
|
+
private pending;
|
|
64
|
+
private connected;
|
|
65
|
+
private readonly reconnectEnabled;
|
|
66
|
+
private readonly reconnectBaseMs;
|
|
67
|
+
private readonly reconnectMaxMs;
|
|
68
|
+
private readonly reconnectHerdJitterMs;
|
|
69
|
+
private readonly reconnectMaxAttempts;
|
|
70
|
+
private readonly refreshAuthToken;
|
|
71
|
+
private readonly onProtocolMismatch;
|
|
72
|
+
private reconnectAttempts;
|
|
73
|
+
private reconnectTimer;
|
|
74
|
+
private closedByClient;
|
|
75
|
+
private authRefreshRequested;
|
|
76
|
+
private authRefreshInFlight;
|
|
77
|
+
/** True after a successful re-mint whose refreshed attempt hasn't authed yet.
|
|
78
|
+
* Caps the mint-succeeds-but-server-rejects cycle (config skew) at ONE
|
|
79
|
+
* refreshed attempt per episode — without it, openSocket → fail → re-mint →
|
|
80
|
+
* openSocket recurses in a hot loop with no backoff. Reset on AUTH_SUCCESS.
|
|
81
|
+
* A FAILED re-mint does not set it: that path already backs off through
|
|
82
|
+
* maybeReconnect, so transient auth-service outages keep retrying. */
|
|
83
|
+
private authRefreshAttempted;
|
|
84
|
+
constructor(options: RoomWsClientOptions);
|
|
85
|
+
/** Subscribe to connection-state transitions (reconnecting/reconnected/failed). */
|
|
86
|
+
onConnectionChange(listener: RoomConnectionListener): () => void;
|
|
87
|
+
private emitConnectionState;
|
|
88
|
+
connect(): Promise<RoomAuthSuccessPayload>;
|
|
89
|
+
private openSocket;
|
|
90
|
+
private maybeReconnect;
|
|
91
|
+
send(msg: RoomClientMessage): void;
|
|
92
|
+
waitFor<T extends RoomServerMessageType>(type: T, predicate?: (msg: RoomServerMessageOf<T>) => boolean, timeoutMs?: number, requestId?: string): Promise<RoomServerMessageOf<T>>;
|
|
93
|
+
waitForAny<T extends RoomServerMessageType>(types: readonly T[], predicate?: (msg: Extract<RoomServerMessage, {
|
|
94
|
+
type: T;
|
|
95
|
+
}>) => boolean, timeoutMs?: number, requestId?: string): Promise<Extract<RoomServerMessage, {
|
|
96
|
+
type: T;
|
|
97
|
+
}>>;
|
|
98
|
+
onMessage(listener: RoomListener): () => void;
|
|
99
|
+
close(): void;
|
|
100
|
+
private readonly waiterHandledErrors;
|
|
101
|
+
serverProtocolVersion(): number | null;
|
|
102
|
+
isGuest(): boolean;
|
|
103
|
+
private record;
|
|
104
|
+
private handleAuthSuccess;
|
|
105
|
+
private retryGuestAuthentication;
|
|
106
|
+
private resolvePending;
|
|
107
|
+
private rejectAll;
|
|
108
|
+
private rejectByRequestId;
|
|
109
|
+
private rejectOldest;
|
|
110
|
+
/** Was this ERROR frame already delivered to a waiting request's caller? */
|
|
111
|
+
errorHandledByWaiter(msg: RoomServerMessage): boolean;
|
|
112
|
+
}
|
|
113
|
+
/** Returns false for an invalid server value so a typo cannot lock out every client. */
|
|
114
|
+
export declare function isRoomClientBuildBelowMinimum(clientBuild: string, minimumBuild: string): boolean;
|
|
115
|
+
export {};
|