@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.
Files changed (87) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +25 -0
  3. package/dist/agent/armed-mode.d.ts +187 -0
  4. package/dist/agent/armed-mode.js +306 -0
  5. package/dist/agent/clients/common.d.ts +70 -0
  6. package/dist/agent/clients/common.js +46 -0
  7. package/dist/agent/lane-draft.d.ts +15 -0
  8. package/dist/agent/lane-draft.js +43 -0
  9. package/dist/agent/lane-prompts.d.ts +102 -0
  10. package/dist/agent/lane-prompts.js +120 -0
  11. package/dist/agent/library-context.d.ts +38 -0
  12. package/dist/agent/library-context.js +57 -0
  13. package/dist/agent/library-model.d.ts +41 -0
  14. package/dist/agent/library-model.js +173 -0
  15. package/dist/agent/room-context.d.ts +151 -0
  16. package/dist/agent/room-context.js +251 -0
  17. package/dist/agent/sidebar-model.d.ts +86 -0
  18. package/dist/agent/sidebar-model.js +162 -0
  19. package/dist/agent/topic-inbox.d.ts +119 -0
  20. package/dist/agent/topic-inbox.js +266 -0
  21. package/dist/agent/topic-view-batcher.d.ts +54 -0
  22. package/dist/agent/topic-view-batcher.js +115 -0
  23. package/dist/client.d.ts +421 -0
  24. package/dist/client.js +1428 -0
  25. package/dist/doc-reconcile.d.ts +100 -0
  26. package/dist/doc-reconcile.js +110 -0
  27. package/dist/doc-uri.d.ts +29 -0
  28. package/dist/doc-uri.js +55 -0
  29. package/dist/endpoints.d.ts +9 -0
  30. package/dist/endpoints.js +13 -0
  31. package/dist/jwt.d.ts +10 -0
  32. package/dist/jwt.js +51 -0
  33. package/dist/library-publisher.d.ts +52 -0
  34. package/dist/library-publisher.js +49 -0
  35. package/dist/merge3.d.ts +50 -0
  36. package/dist/merge3.js +280 -0
  37. package/dist/names.d.ts +6 -0
  38. package/dist/names.js +35 -0
  39. package/dist/shared/emoji-data.d.ts +22 -0
  40. package/dist/shared/emoji-data.js +8834 -0
  41. package/dist/shared/mentions.d.ts +6 -0
  42. package/dist/shared/mentions.js +32 -0
  43. package/dist/shared/rooms-protocol.d.ts +1183 -0
  44. package/dist/shared/rooms-protocol.js +160 -0
  45. package/dist/social-client.d.ts +361 -0
  46. package/dist/social-client.js +686 -0
  47. package/dist/social-types.d.ts +338 -0
  48. package/dist/social-types.js +1 -0
  49. package/dist/suggestion-attribution.d.ts +10 -0
  50. package/dist/suggestion-attribution.js +56 -0
  51. package/dist/topic-uri.d.ts +26 -0
  52. package/dist/topic-uri.js +40 -0
  53. package/dist/types.d.ts +2 -0
  54. package/dist/types.js +1 -0
  55. package/dist/version.d.ts +2 -0
  56. package/dist/version.js +2 -0
  57. package/dist/ws-client.d.ts +115 -0
  58. package/dist/ws-client.js +491 -0
  59. package/package.json +180 -0
  60. package/src/agent/armed-mode.ts +368 -0
  61. package/src/agent/clients/common.ts +91 -0
  62. package/src/agent/lane-draft.ts +47 -0
  63. package/src/agent/lane-prompts.ts +267 -0
  64. package/src/agent/library-context.ts +97 -0
  65. package/src/agent/library-model.ts +210 -0
  66. package/src/agent/room-context.ts +351 -0
  67. package/src/agent/sidebar-model.ts +235 -0
  68. package/src/agent/topic-inbox.ts +297 -0
  69. package/src/agent/topic-view-batcher.ts +134 -0
  70. package/src/client.ts +2331 -0
  71. package/src/doc-reconcile.ts +160 -0
  72. package/src/doc-uri.ts +83 -0
  73. package/src/endpoints.ts +14 -0
  74. package/src/jwt.ts +59 -0
  75. package/src/library-publisher.ts +93 -0
  76. package/src/merge3.ts +326 -0
  77. package/src/names.ts +44 -0
  78. package/src/shared/emoji-data.ts +8868 -0
  79. package/src/shared/mentions.ts +32 -0
  80. package/src/shared/rooms-protocol.ts +1339 -0
  81. package/src/social-client.ts +1287 -0
  82. package/src/social-types.ts +376 -0
  83. package/src/suggestion-attribution.ts +83 -0
  84. package/src/topic-uri.ts +64 -0
  85. package/src/types.ts +83 -0
  86. package/src/version.ts +2 -0
  87. package/src/ws-client.ts +611 -0
package/dist/merge3.js ADDED
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Line-based three-way merge (diff3 semantics) for the library mirror's
3
+ * agent-mediated conflict resolution. Pure, no deps, byte-faithful: content
4
+ * is split on "\n" only and re-joined identically, so CRLF and no-trailing-
5
+ * newline round-trip exactly.
6
+ *
7
+ * The contract callers rely on:
8
+ * - `clean: true` means every collision region was one-sided or identical on
9
+ * both sides; `merged` is the full merged document.
10
+ * - `clean: false` means at least one region genuinely diverged (or an input
11
+ * bound tripped); `merged` is "" so no caller can write a half-merge, and
12
+ * `regions` carries the diverging chunks for a mediator to look at.
13
+ * - Identical changes on BOTH sides merge clean. This is load-bearing: a
14
+ * crash between the merged-file write and the manifest save re-runs the
15
+ * merge with mine already containing theirs' lines, and it must converge.
16
+ * - Touching-or-overlapping changes from the two sides conflict (git's
17
+ * conservative rule); insertions at the same anchor conflict.
18
+ */
19
+ /** Reassemble a skeleton with one resolved text per region ("" = the region
20
+ * resolves to nothing). The counterpart of a region-scoped mediation. */
21
+ export function spliceSkeleton(skeleton, resolved) {
22
+ const lines = [];
23
+ for (const part of skeleton) {
24
+ if (part.kind === "lines") {
25
+ lines.push(...part.lines);
26
+ }
27
+ else {
28
+ const text = resolved[part.index];
29
+ if (text === undefined)
30
+ throw new Error(`missing resolution for region ${part.index}`);
31
+ if (text !== "")
32
+ lines.push(...text.split("\n"));
33
+ }
34
+ }
35
+ return lines.join("\n");
36
+ }
37
+ const DEFAULT_MAX_LINES = 30_000;
38
+ const DEFAULT_MAX_EDIT_DISTANCE = 2_048;
39
+ /**
40
+ * Myers O(ND) shortest-edit-script diff at line granularity, returning
41
+ * base-anchored replacement hunks. Returns null when the edit distance
42
+ * exceeds maxD (caller bails to not-clean, which degrades to the existing
43
+ * theirs-file behavior).
44
+ */
45
+ function diffLines(a, b, maxD) {
46
+ // Trim the common prefix/suffix first: cheap, and it keeps the Myers band
47
+ // proportional to the actual change, not the file.
48
+ let start = 0;
49
+ const minLen = Math.min(a.length, b.length);
50
+ while (start < minLen && a[start] === b[start])
51
+ start++;
52
+ let endA = a.length;
53
+ let endB = b.length;
54
+ while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
55
+ endA--;
56
+ endB--;
57
+ }
58
+ const n = endA - start;
59
+ const m = endB - start;
60
+ if (n === 0 && m === 0)
61
+ return [];
62
+ if (n === 0)
63
+ return [{ aStart: start, aEnd: start, bStart: start, bEnd: endB }];
64
+ if (m === 0)
65
+ return [{ aStart: start, aEnd: endA, bStart: start, bEnd: start }];
66
+ const max = Math.min(n + m, maxD);
67
+ const offset = max;
68
+ const width = 2 * max + 1;
69
+ let v = new Int32Array(width);
70
+ const trace = [];
71
+ let foundD = -1;
72
+ outer: for (let d = 0; d <= max; d++) {
73
+ trace.push(v.slice());
74
+ const next = v.slice();
75
+ for (let k = -d; k <= d; k += 2) {
76
+ if (k < -max || k > max)
77
+ continue;
78
+ let x;
79
+ if (k === -d || (k !== d && (v[k - 1 + offset] ?? 0) < (v[k + 1 + offset] ?? 0))) {
80
+ x = v[k + 1 + offset] ?? 0;
81
+ }
82
+ else {
83
+ x = (v[k - 1 + offset] ?? 0) + 1;
84
+ }
85
+ let y = x - k;
86
+ while (x < n && y < m && a[start + x] === b[start + y]) {
87
+ x++;
88
+ y++;
89
+ }
90
+ next[k + offset] = x;
91
+ if (x >= n && y >= m) {
92
+ v = next;
93
+ foundD = d;
94
+ break outer;
95
+ }
96
+ }
97
+ v = next;
98
+ }
99
+ if (foundD < 0)
100
+ return null;
101
+ const edits = [];
102
+ let x = n;
103
+ let y = m;
104
+ for (let d = foundD; d > 0; d--) {
105
+ const prev = trace[d];
106
+ if (!prev)
107
+ return null;
108
+ const k = x - y;
109
+ let prevK;
110
+ if (k === -d || (k !== d && (prev[k - 1 + offset] ?? 0) < (prev[k + 1 + offset] ?? 0))) {
111
+ prevK = k + 1;
112
+ }
113
+ else {
114
+ prevK = k - 1;
115
+ }
116
+ const prevX = prev[prevK + offset] ?? 0;
117
+ const prevY = prevX - prevK;
118
+ if (prevK === k + 1) {
119
+ // Vertical move: b[prevY] was inserted at base position prevX.
120
+ edits.push({ type: "ins", x: prevX, y: prevY });
121
+ }
122
+ else {
123
+ // Horizontal move: a[prevX] was deleted.
124
+ edits.push({ type: "del", x: prevX, y: prevY });
125
+ }
126
+ x = prevX;
127
+ y = prevY;
128
+ }
129
+ edits.reverse();
130
+ const hunks = [];
131
+ let current = null;
132
+ let pathX = -1;
133
+ let pathY = -1;
134
+ for (const edit of edits) {
135
+ const contiguous = current !== null && edit.x === pathX && edit.y === pathY;
136
+ if (!contiguous) {
137
+ if (current)
138
+ hunks.push(current);
139
+ current = { aStart: edit.x, aEnd: edit.x, bStart: edit.y, bEnd: edit.y };
140
+ }
141
+ if (!current)
142
+ continue;
143
+ if (edit.type === "del") {
144
+ current.aEnd = edit.x + 1;
145
+ pathX = edit.x + 1;
146
+ pathY = edit.y;
147
+ }
148
+ else {
149
+ current.bEnd = edit.y + 1;
150
+ pathX = edit.x;
151
+ pathY = edit.y + 1;
152
+ }
153
+ }
154
+ if (current)
155
+ hunks.push(current);
156
+ // Re-anchor to full coordinates (the prefix trim shifted everything).
157
+ return hunks.map((hunk) => ({
158
+ aStart: hunk.aStart + start,
159
+ aEnd: hunk.aEnd + start,
160
+ bStart: hunk.bStart + start,
161
+ bEnd: hunk.bEnd + start,
162
+ }));
163
+ }
164
+ /** A side's replacement lines for base range [aStart,aEnd), given the side's
165
+ * cluster hunks (linear offset mapping holds between hunks). */
166
+ function sideSlice(sideLines, baseLines, hunks, aStart, aEnd) {
167
+ if (hunks.length === 0)
168
+ return baseLines.slice(aStart, aEnd);
169
+ const first = hunks[0];
170
+ const last = hunks[hunks.length - 1];
171
+ const sStart = first.bStart - (first.aStart - aStart);
172
+ const sEnd = last.bEnd + (aEnd - last.aEnd);
173
+ return sideLines.slice(sStart, sEnd);
174
+ }
175
+ function sameLines(a, b) {
176
+ if (a.length !== b.length)
177
+ return false;
178
+ for (let i = 0; i < a.length; i++) {
179
+ if (a[i] !== b[i])
180
+ return false;
181
+ }
182
+ return true;
183
+ }
184
+ function notClean(base, mine, theirs) {
185
+ return {
186
+ clean: false,
187
+ merged: "",
188
+ regions: [{ base, mine, theirs }],
189
+ skeleton: [{ kind: "region", index: 0 }],
190
+ };
191
+ }
192
+ export function merge3(base, mine, theirs, options = {}) {
193
+ if (mine === theirs)
194
+ return { clean: true, merged: mine, regions: [] };
195
+ if (base === mine)
196
+ return { clean: true, merged: theirs, regions: [] };
197
+ if (base === theirs)
198
+ return { clean: true, merged: mine, regions: [] };
199
+ const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
200
+ const maxD = options.maxEditDistance ?? DEFAULT_MAX_EDIT_DISTANCE;
201
+ const baseLines = base.split("\n");
202
+ const mineLines = mine.split("\n");
203
+ const theirsLines = theirs.split("\n");
204
+ if (baseLines.length > maxLines || mineLines.length > maxLines || theirsLines.length > maxLines) {
205
+ return notClean(baseLines, mineLines, theirsLines);
206
+ }
207
+ const mineDiff = diffLines(baseLines, mineLines, maxD);
208
+ const theirsDiff = diffLines(baseLines, theirsLines, maxD);
209
+ if (!mineDiff || !theirsDiff)
210
+ return notClean(baseLines, mineLines, theirsLines);
211
+ const out = [];
212
+ const regions = [];
213
+ const skeleton = [];
214
+ const emitLines = (lines) => {
215
+ if (lines.length === 0)
216
+ return;
217
+ out.push(...lines);
218
+ const last = skeleton[skeleton.length - 1];
219
+ if (last?.kind === "lines")
220
+ last.lines.push(...lines);
221
+ else
222
+ skeleton.push({ kind: "lines", lines: [...lines] });
223
+ };
224
+ let cursor = 0;
225
+ let mi = 0;
226
+ let ti = 0;
227
+ while (mi < mineDiff.length || ti < theirsDiff.length) {
228
+ const nextMine = mineDiff[mi];
229
+ const nextTheirs = theirsDiff[ti];
230
+ const clusterStart = Math.min(nextMine ? nextMine.aStart : Number.POSITIVE_INFINITY, nextTheirs ? nextTheirs.aStart : Number.POSITIVE_INFINITY);
231
+ // Copy the untouched base span before the next change.
232
+ emitLines(baseLines.slice(cursor, clusterStart));
233
+ cursor = clusterStart;
234
+ // Grow the cluster while hunks from either side touch it. Touching (not
235
+ // just overlapping) is deliberate: adjacent edits conflict, like git.
236
+ let clusterEnd = clusterStart;
237
+ const mineFirst = mi;
238
+ const theirsFirst = ti;
239
+ let grew = true;
240
+ while (grew) {
241
+ grew = false;
242
+ while (mi < mineDiff.length && mineDiff[mi].aStart <= clusterEnd) {
243
+ clusterEnd = Math.max(clusterEnd, mineDiff[mi].aEnd);
244
+ mi++;
245
+ grew = true;
246
+ }
247
+ while (ti < theirsDiff.length && theirsDiff[ti].aStart <= clusterEnd) {
248
+ clusterEnd = Math.max(clusterEnd, theirsDiff[ti].aEnd);
249
+ ti++;
250
+ grew = true;
251
+ }
252
+ }
253
+ const mineHunks = mineDiff.slice(mineFirst, mi);
254
+ const theirsHunks = theirsDiff.slice(theirsFirst, ti);
255
+ const mineChunk = sideSlice(mineLines, baseLines, mineHunks, clusterStart, clusterEnd);
256
+ const theirsChunk = sideSlice(theirsLines, baseLines, theirsHunks, clusterStart, clusterEnd);
257
+ if (theirsHunks.length === 0) {
258
+ emitLines(mineChunk);
259
+ }
260
+ else if (mineHunks.length === 0) {
261
+ emitLines(theirsChunk);
262
+ }
263
+ else if (sameLines(mineChunk, theirsChunk)) {
264
+ emitLines(mineChunk);
265
+ }
266
+ else {
267
+ skeleton.push({ kind: "region", index: regions.length });
268
+ regions.push({
269
+ base: baseLines.slice(clusterStart, clusterEnd),
270
+ mine: mineChunk,
271
+ theirs: theirsChunk,
272
+ });
273
+ }
274
+ cursor = clusterEnd;
275
+ }
276
+ emitLines(baseLines.slice(cursor));
277
+ if (regions.length > 0)
278
+ return { clean: false, merged: "", regions, skeleton };
279
+ return { clean: true, merged: out.join("\n"), regions: [] };
280
+ }
@@ -0,0 +1,6 @@
1
+ export declare const RESERVED_ROOM_PREFIXES: string[];
2
+ export declare function normalizeRoomNameInput(raw: string): string;
3
+ export declare function normalizeCommunityRoomName(raw: string): string;
4
+ export declare function assertCommunityRoomName(name: string): void;
5
+ export declare function normalizeWorkspaceId(raw: string): string;
6
+ export declare function workspaceRoomName(workspaceId: string): string;
package/dist/names.js ADDED
@@ -0,0 +1,35 @@
1
+ export const RESERVED_ROOM_PREFIXES = ["workspace-"];
2
+ const ROOM_NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,78}[a-z0-9])?$/;
3
+ const WORKSPACE_ID_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,126}[A-Za-z0-9])?$/;
4
+ export function normalizeRoomNameInput(raw) {
5
+ return raw.trim().replace(/^#/, "").toLowerCase();
6
+ }
7
+ export function normalizeCommunityRoomName(raw) {
8
+ const name = normalizeRoomNameInput(raw);
9
+ assertCommunityRoomName(name);
10
+ return name;
11
+ }
12
+ export function assertCommunityRoomName(name) {
13
+ assertRoomSlug(name, "Room name");
14
+ if (RESERVED_ROOM_PREFIXES.some((prefix) => name.startsWith(prefix))) {
15
+ throw new Error("Room names cannot use the reserved workspace- prefix");
16
+ }
17
+ if (/^room-[a-f0-9]{12}$/.test(name)) {
18
+ throw new Error("Room names cannot use the reserved generated room id format");
19
+ }
20
+ }
21
+ export function normalizeWorkspaceId(raw) {
22
+ const id = raw.trim();
23
+ if (!WORKSPACE_ID_PATTERN.test(id)) {
24
+ throw new Error("Workspace ID must contain only letters, numbers, and hyphens");
25
+ }
26
+ return id;
27
+ }
28
+ export function workspaceRoomName(workspaceId) {
29
+ return `workspace-${normalizeWorkspaceId(workspaceId).toLowerCase()}`;
30
+ }
31
+ function assertRoomSlug(value, label) {
32
+ if (!ROOM_NAME_PATTERN.test(value)) {
33
+ throw new Error(`${label} must be 1-80 characters, start and end with a letter or number, and contain only lowercase letters, numbers, and hyphens`);
34
+ }
35
+ }
@@ -0,0 +1,22 @@
1
+ export type EmojiCategory = "smileys" | "people" | "nature" | "food" | "travel" | "activities" | "objects" | "symbols" | "flags";
2
+ export interface EmojiEntry {
3
+ /** The native glyph, e.g. "🔥" */
4
+ glyph: string;
5
+ /** Canonical shortcode without colons, e.g. "fire" */
6
+ shortcode: string;
7
+ /** Search keywords (lowercase), including the shortcode */
8
+ keywords: string[];
9
+ /** One of the 9 category keys in EMOJI_CATEGORIES */
10
+ category: EmojiCategory;
11
+ }
12
+ /** Picker display order for categories; matches the dataset's sort order. */
13
+ export declare const EMOJI_CATEGORIES: readonly EmojiCategory[];
14
+ export declare const EMOJI: readonly EmojiEntry[];
15
+ /** The hover-toolbar reaction trio; every glyph exists in EMOJI. */
16
+ export declare const QUICK_REACTIONS: readonly string[];
17
+ /**
18
+ * Prefix + substring search over shortcodes and keywords. Shortcode-prefix
19
+ * matches rank first (shortest shortcode wins ties), then keyword-prefix,
20
+ * then shortcode/keyword substring matches.
21
+ */
22
+ export declare function searchEmoji(query: string, limit?: number): EmojiEntry[];