@khorsheed/dsh-room 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 (95) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +48 -0
  4. package/README.zh.md +48 -0
  5. package/cordis.patch.yml +6 -0
  6. package/lib/client.js +11481 -0
  7. package/lib/index.js +3231 -0
  8. package/lib/invariant.js +26 -0
  9. package/lib/journal-CuRnWk66.js +435 -0
  10. package/lib/tool.js +350 -0
  11. package/lib/typert.host.d.ts +3 -0
  12. package/lib/typert.host.js +2615 -0
  13. package/lib/typert.remote-client.d.ts +55 -0
  14. package/lib/typert.remote-client.js +1711 -0
  15. package/lib/types/adapter.d.ts +42 -0
  16. package/lib/types/adapter.js +41 -0
  17. package/lib/types/agent-setup.d.ts +141 -0
  18. package/lib/types/agent-setup.js +145 -0
  19. package/lib/types/client/InviteAgentAction.d.ts +13 -0
  20. package/lib/types/client/InviteAgentAction.js +45 -0
  21. package/lib/types/client/InviteDialog.d.ts +90 -0
  22. package/lib/types/client/InviteDialog.js +216 -0
  23. package/lib/types/client/MemberCard.d.ts +33 -0
  24. package/lib/types/client/MemberCard.js +32 -0
  25. package/lib/types/client/MembersView.d.ts +5 -0
  26. package/lib/types/client/MembersView.js +168 -0
  27. package/lib/types/client/RoomComposer.d.ts +21 -0
  28. package/lib/types/client/RoomComposer.js +211 -0
  29. package/lib/types/client/RoomDockCapsules.d.ts +64 -0
  30. package/lib/types/client/RoomDockCapsules.js +277 -0
  31. package/lib/types/client/RoomEventView.d.ts +11 -0
  32. package/lib/types/client/RoomEventView.js +17 -0
  33. package/lib/types/client/RoomModelPicker.d.ts +18 -0
  34. package/lib/types/client/RoomModelPicker.js +134 -0
  35. package/lib/types/client/RoomPlanView.d.ts +17 -0
  36. package/lib/types/client/RoomPlanView.js +88 -0
  37. package/lib/types/client/RoomQueueStrip.d.ts +26 -0
  38. package/lib/types/client/RoomQueueStrip.js +28 -0
  39. package/lib/types/client/RoomRecoveryView.d.ts +11 -0
  40. package/lib/types/client/RoomRecoveryView.js +38 -0
  41. package/lib/types/client/RoomRelayView.d.ts +12 -0
  42. package/lib/types/client/RoomRelayView.js +20 -0
  43. package/lib/types/client/RoomRunView.d.ts +15 -0
  44. package/lib/types/client/RoomRunView.js +50 -0
  45. package/lib/types/client/RoomSpeechView.d.ts +18 -0
  46. package/lib/types/client/RoomSpeechView.js +62 -0
  47. package/lib/types/client/RoomStatsLine.d.ts +61 -0
  48. package/lib/types/client/RoomStatsLine.js +138 -0
  49. package/lib/types/client/RoomTaskLineView.d.ts +13 -0
  50. package/lib/types/client/RoomTaskLineView.js +21 -0
  51. package/lib/types/client/RoomTodoStrip.d.ts +24 -0
  52. package/lib/types/client/RoomTodoStrip.js +64 -0
  53. package/lib/types/client/format.d.ts +31 -0
  54. package/lib/types/client/format.js +48 -0
  55. package/lib/types/client/icons.d.ts +27 -0
  56. package/lib/types/client/icons.js +11 -0
  57. package/lib/types/client/index.d.ts +39 -0
  58. package/lib/types/client/index.js +471 -0
  59. package/lib/types/client/locales.d.ts +422 -0
  60. package/lib/types/client/locales.js +413 -0
  61. package/lib/types/client/member-color.d.ts +13 -0
  62. package/lib/types/client/member-color.js +22 -0
  63. package/lib/types/client/name-pool.d.ts +17 -0
  64. package/lib/types/client/name-pool.js +25 -0
  65. package/lib/types/client/nodes.d.ts +136 -0
  66. package/lib/types/client/nodes.js +212 -0
  67. package/lib/types/client/preset-visibility.d.ts +91 -0
  68. package/lib/types/client/preset-visibility.js +113 -0
  69. package/lib/types/client/request-ids.d.ts +7 -0
  70. package/lib/types/client/request-ids.js +21 -0
  71. package/lib/types/client/room-store.d.ts +137 -0
  72. package/lib/types/client/room-store.js +244 -0
  73. package/lib/types/client/slots.d.ts +319 -0
  74. package/lib/types/client/slots.js +1 -0
  75. package/lib/types/deliveries.d.ts +5 -0
  76. package/lib/types/deliveries.js +20 -0
  77. package/lib/types/dispatch.d.ts +105 -0
  78. package/lib/types/dispatch.js +467 -0
  79. package/lib/types/index.d.ts +301 -0
  80. package/lib/types/index.js +1200 -0
  81. package/lib/types/invariant.d.ts +16 -0
  82. package/lib/types/invariant.js +24 -0
  83. package/lib/types/journal.d.ts +117 -0
  84. package/lib/types/journal.js +434 -0
  85. package/lib/types/plan-service.d.ts +34 -0
  86. package/lib/types/plan-service.js +266 -0
  87. package/lib/types/plan.d.ts +168 -0
  88. package/lib/types/plan.js +494 -0
  89. package/lib/types/tool.d.ts +56 -0
  90. package/lib/types/tool.js +389 -0
  91. package/lib/types/types.d.ts +813 -0
  92. package/lib/types/types.js +1 -0
  93. package/locale/en.json +6 -0
  94. package/locale/zh.json +6 -0
  95. package/package.json +131 -0
@@ -0,0 +1,26 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@khorsheed/dsh-room`.
4
+ * @module @khorsheed/dsh-room/invariant
5
+ */
6
+ const PACKAGE_NAME = "@khorsheed/dsh-room";
7
+ /** Cordis companion plugin name. */
8
+ const name = "room-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /**
12
+ * Placeholder (Step 0/1 spike): no runtime invariant yet. The `room/created`
13
+ * marker write passes through Session.append, whose validation rejects any
14
+ * non-JSON payload, and the client contributions ride the slot/locale
15
+ * lifecycles disposed with the plugin fiber. Later steps (roster transition
16
+ * validation, dispatch invariants) install real checks here.
17
+ */
18
+ const install = () => {};
19
+ /**
20
+ * Register this package's invariant companion.
21
+ * @param ctx - Cordis context carrying the invariant service.
22
+ * @returns the installed registration's disposer after setup succeeds.
23
+ */
24
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
25
+ //#endregion
26
+ export { apply, inject, name };
@@ -0,0 +1,435 @@
1
+ //#region lib/types/deliveries.js
2
+ /** Accepted delivery intents and their latest durable outcomes. */
3
+ function replayDeliveries(events) {
4
+ const rows = /* @__PURE__ */ new Map();
5
+ for (const event of events) if (event.type === "room/dispatch" && event.data.targetIds !== void 0) for (const memberId of event.data.targetIds) {
6
+ const id = `${event.seq}:${memberId}`;
7
+ if (!rows.has(id)) rows.set(id, {
8
+ id,
9
+ dispatchSeq: event.seq,
10
+ memberId,
11
+ status: "queued",
12
+ text: event.data.text,
13
+ origin: event.data.origin ?? "human",
14
+ ...event.data.plan === void 0 ? {} : { plan: event.data.plan }
15
+ });
16
+ }
17
+ else if (event.type === "room/delivery-state") {
18
+ const row = rows.get(event.data.id);
19
+ if (row === void 0 || row.memberId !== event.data.memberId || row.dispatchSeq !== event.data.dispatchSeq) continue;
20
+ rows.set(row.id, {
21
+ ...row,
22
+ status: event.data.state,
23
+ ...event.data.error === void 0 ? {} : { error: event.data.error }
24
+ });
25
+ }
26
+ return [...rows.values()];
27
+ }
28
+ //#endregion
29
+ //#region lib/types/journal.js
30
+ /**
31
+ * Room journal replay: folds the room session's `room/*` custom events into
32
+ * the derived {@link RoomState} — roster, notification relays, the task
33
+ * board, and run states. Pure: the Remote surface reads through it, the
34
+ * dispatch engine reads instruction cursors through it, and the unit tests
35
+ * exercise it without a cordis composition. Reload-replay recovers the exact
36
+ * same state, which is what makes the journal the room's single source of
37
+ * truth. There is NO blackboard fold: member prompts never consume the
38
+ * room's running log (see the design note) — speech/dispatch events are
39
+ * journaled for UI projection and replay only.
40
+ * @module @khorsheed/dsh-room/journal
41
+ */
42
+ /**
43
+ * The native DSH member's addressing name in new rooms. It starts as the
44
+ * coordinator; bare messages follow the selected coordinator thereafter.
45
+ * Existing journals retain their recorded names, including legacy main.
46
+ */
47
+ const MAIN_AGENT_MEMBER = "dsh";
48
+ /**
49
+ * Every `room/*` session-event type this package introduces. Room registers
50
+ * them into the harness's KNOWN_SESSION_EVENT_TYPES persistence catalog at
51
+ * apply time (see the RoomService constructor): the catalog's read-path
52
+ * refusal exists so a build that does NOT understand an event type never
53
+ * silently mangles the log — a build with room mounted understands these, so
54
+ * registration is the semantically correct declaration, and a build without
55
+ * room keeps refusing them. The catalog's own header defers a registration
56
+ * surface for out-of-repo plugins "until such a consumer exists"; this
57
+ * in-place add is that surface's temporary form — migrate to the official
58
+ * one when it lands upstream.
59
+ */
60
+ const ROOM_EVENT_TYPES = [
61
+ "room/created",
62
+ "room/plan-state",
63
+ "room/coordinator",
64
+ "room/delivery-state",
65
+ "room/member-added",
66
+ "room/member-updated",
67
+ "room/member-removed",
68
+ "room/dispatch",
69
+ "room/speech",
70
+ "room/run-state",
71
+ "room/relay",
72
+ "room/relay-resolved",
73
+ "room/task-added",
74
+ "room/task-updated",
75
+ "room/task-edited",
76
+ "room/goal"
77
+ ];
78
+ /** Whether an event log carries the room identity marker. */
79
+ function isRoomLog(events) {
80
+ return events.some((event) => event.type === "room/created");
81
+ }
82
+ /**
83
+ * Parse the leading `@name` tokens of a raw composer message.
84
+ * @param raw - the raw text.
85
+ * @returns the addressed names (deduped, order-preserving) and the body with
86
+ * the mention prefix stripped. Only LEADING tokens address: an `@name`
87
+ * inside prose is plain text (a bare message goes to the selected coordinator).
88
+ */
89
+ function parseMentions(raw) {
90
+ const targets = [];
91
+ let rest = raw.trim();
92
+ for (;;) {
93
+ const match = /^@(\S+)(?:\s+|$)/.exec(rest);
94
+ if (match === null) break;
95
+ const name = match[1];
96
+ if (!targets.includes(name)) targets.push(name);
97
+ rest = rest.slice(match[0].length).trim();
98
+ }
99
+ return {
100
+ targets,
101
+ text: rest
102
+ };
103
+ }
104
+ /**
105
+ * The fallback notification channel: a member reply's TRAILING own line of
106
+ * the form `@name <content>` (the format the roster injection teaches).
107
+ * Only the last line counts — an `@name` inside prose is a mention, not a
108
+ * notification. Leading whitespace on that line is tolerated (models indent
109
+ * under their own bullet/list style). The caller checks the target against
110
+ * the roster.
111
+ * @param text - the member's reply text.
112
+ * @returns the parsed directive, or undefined.
113
+ */
114
+ function parseRelayDirective(text) {
115
+ const trimmed = text.trimEnd();
116
+ if (trimmed === "") return void 0;
117
+ const last = trimmed.slice(trimmed.lastIndexOf("\n") + 1).trimStart();
118
+ const match = /^@(\S+)\s+(\S[\s\S]*)$/.exec(last);
119
+ if (match === null) return void 0;
120
+ return {
121
+ to: match[1],
122
+ content: match[2].trim()
123
+ };
124
+ }
125
+ /**
126
+ * Fold a session's event log into the room state.
127
+ *
128
+ * Write-side invariants (invite/update/remove validate before appending) are
129
+ * NOT re-enforced here: a replayed log may be hand-edited or forked, so the
130
+ * fold is defensive — a duplicate member-added keeps the first record (first
131
+ * wins, a replay cannot re-seat a member), and updates/removals naming an
132
+ * unknown member are dropped. Unknown event types (e.g. a `room/note` from
133
+ * the dropped blackboard design) fall through the switch and are skipped.
134
+ * Removal also drops the member's run state: the roster reads clean after a
135
+ * remove. Relays and tasks fold by id, latest resolution winning.
136
+ * @param events - the session's event log.
137
+ * @returns the folded room state (empty for a non-room log).
138
+ */
139
+ function replay(events) {
140
+ const members = [];
141
+ const byName = /* @__PURE__ */ new Map();
142
+ const relays = [];
143
+ const relayById = /* @__PURE__ */ new Map();
144
+ const tasks = [];
145
+ const taskById = /* @__PURE__ */ new Map();
146
+ const runs = /* @__PURE__ */ new Map();
147
+ let goal;
148
+ let coordinator;
149
+ for (const event of events) switch (event.type) {
150
+ case "room/coordinator":
151
+ if (event.data.revision > (coordinator?.revision ?? 0)) coordinator = event.data;
152
+ break;
153
+ case "room/member-added": {
154
+ if (byName.has(event.data.name)) break;
155
+ const added = {
156
+ id: event.data.id ?? `legacy:${event.seq}`,
157
+ name: event.data.name,
158
+ kind: event.data.kind,
159
+ invitedBy: event.data.invitedBy,
160
+ ...event.data.provider === void 0 ? {} : { provider: event.data.provider },
161
+ ...event.data.instructions === void 0 ? {} : { instructions: event.data.instructions },
162
+ ...event.data.cwd === void 0 ? {} : { cwd: event.data.cwd },
163
+ ...event.data.model === void 0 ? {} : { model: event.data.model },
164
+ ...event.data.childSessionId === void 0 ? {} : { childSessionId: event.data.childSessionId }
165
+ };
166
+ byName.set(added.name, added);
167
+ members.push(added);
168
+ break;
169
+ }
170
+ case "room/member-updated": {
171
+ const member = byName.get(event.data.name);
172
+ if (member === void 0) break;
173
+ const withoutInstructions = event.data.instructions === null ? (({ instructions: _cleared, ...rest }) => rest)(member) : {
174
+ ...member,
175
+ ...event.data.instructions === void 0 ? {} : { instructions: event.data.instructions }
176
+ };
177
+ const withoutCwd = event.data.cwd === null ? (({ cwd: _cleared, ...rest }) => rest)(withoutInstructions) : {
178
+ ...withoutInstructions,
179
+ ...event.data.cwd === void 0 ? {} : { cwd: event.data.cwd }
180
+ };
181
+ const patched = {
182
+ ...event.data.model === null ? (({ model: _cleared, ...rest }) => rest)(withoutCwd) : {
183
+ ...withoutCwd,
184
+ ...event.data.model === void 0 ? {} : { model: event.data.model }
185
+ },
186
+ ...event.data.childSessionId === void 0 ? {} : { childSessionId: event.data.childSessionId }
187
+ };
188
+ const rename = event.data.rename;
189
+ if (rename === void 0 || rename === member.name || byName.has(rename)) {
190
+ byName.set(member.name, patched);
191
+ members[members.indexOf(member)] = patched;
192
+ break;
193
+ }
194
+ const renamed = {
195
+ ...patched,
196
+ name: rename
197
+ };
198
+ byName.delete(member.name);
199
+ byName.set(rename, renamed);
200
+ members[members.indexOf(member)] = renamed;
201
+ for (let index = 0; index < tasks.length; index++) {
202
+ const task = tasks[index];
203
+ if (task.member !== member.name && task.blockedBy !== member.name) continue;
204
+ const migrated = {
205
+ ...task,
206
+ ...task.member === member.name ? { member: rename } : {},
207
+ ...task.blockedBy === member.name ? { blockedBy: rename } : {}
208
+ };
209
+ taskById.set(migrated.id, migrated);
210
+ tasks[index] = migrated;
211
+ }
212
+ for (let index = 0; index < relays.length; index++) {
213
+ const relay = relays[index];
214
+ if (relay.from !== member.name && relay.to !== member.name) continue;
215
+ const migrated = {
216
+ ...relay,
217
+ from: relay.from === member.name ? rename : relay.from,
218
+ to: relay.to === member.name ? rename : relay.to
219
+ };
220
+ relayById.set(migrated.id, migrated);
221
+ relays[index] = migrated;
222
+ }
223
+ const run = runs.get(member.name);
224
+ if (run !== void 0) {
225
+ runs.delete(member.name);
226
+ runs.set(rename, {
227
+ ...run,
228
+ member: rename
229
+ });
230
+ }
231
+ break;
232
+ }
233
+ case "room/member-removed": {
234
+ const member = byName.get(event.data.name);
235
+ if (member === void 0) break;
236
+ byName.delete(event.data.name);
237
+ members.splice(members.indexOf(member), 1);
238
+ runs.delete(event.data.name);
239
+ break;
240
+ }
241
+ case "room/relay": {
242
+ if (relayById.has(event.data.id)) break;
243
+ const relay = {
244
+ id: event.data.id,
245
+ from: event.data.from,
246
+ to: event.data.to,
247
+ content: event.data.content,
248
+ state: "pending",
249
+ ...event.data.provenance === void 0 ? {} : { provenance: event.data.provenance }
250
+ };
251
+ relayById.set(relay.id, relay);
252
+ relays.push(relay);
253
+ break;
254
+ }
255
+ case "room/relay-resolved": {
256
+ const relay = relayById.get(event.data.id);
257
+ if (relay === void 0) break;
258
+ const resolved = {
259
+ ...relay,
260
+ state: event.data.state
261
+ };
262
+ relayById.set(resolved.id, resolved);
263
+ relays[relays.indexOf(relay)] = resolved;
264
+ break;
265
+ }
266
+ case "room/task-added": {
267
+ if (taskById.has(event.data.id)) break;
268
+ const task = {
269
+ id: event.data.id,
270
+ member: event.data.member,
271
+ title: event.data.title,
272
+ ...event.data.deliveryId === void 0 ? {} : { deliveryId: event.data.deliveryId },
273
+ status: event.data.status,
274
+ ...event.data.blockedBy === void 0 ? {} : { blockedBy: event.data.blockedBy },
275
+ updatedAt: event.time
276
+ };
277
+ taskById.set(task.id, task);
278
+ tasks.push(task);
279
+ break;
280
+ }
281
+ case "room/task-updated": {
282
+ const task = taskById.get(event.data.id);
283
+ if (task === void 0) break;
284
+ const updated = {
285
+ ...task,
286
+ status: event.data.status,
287
+ updatedAt: event.time
288
+ };
289
+ taskById.set(updated.id, updated);
290
+ tasks[tasks.indexOf(task)] = updated;
291
+ break;
292
+ }
293
+ case "room/task-edited": {
294
+ const task = taskById.get(event.data.id);
295
+ if (task === void 0) break;
296
+ const edited = {
297
+ ...event.data.blockedBy === null ? (({ blockedBy: _cleared, ...rest }) => rest)(task) : {
298
+ ...task,
299
+ ...event.data.blockedBy === void 0 ? {} : { blockedBy: event.data.blockedBy }
300
+ },
301
+ ...event.data.title === void 0 ? {} : { title: event.data.title },
302
+ updatedAt: event.time
303
+ };
304
+ taskById.set(edited.id, edited);
305
+ tasks[tasks.indexOf(task)] = edited;
306
+ break;
307
+ }
308
+ case "room/goal": {
309
+ const text = event.data.text.trim();
310
+ goal = text === "" ? void 0 : text;
311
+ break;
312
+ }
313
+ case "room/run-state": {
314
+ const current = runs.get(event.data.member);
315
+ if (event.data.state !== "running" && event.data.runId !== void 0 && current?.runId !== void 0 && current.runId !== event.data.runId) break;
316
+ runs.set(event.data.member, {
317
+ member: event.data.member,
318
+ state: event.data.state,
319
+ startedAt: event.data.startedAt,
320
+ ...event.data.runId === void 0 ? {} : { runId: event.data.runId },
321
+ ...event.data.elapsedMs === void 0 ? {} : { elapsedMs: event.data.elapsedMs },
322
+ ...event.data.error === void 0 ? {} : { error: event.data.error }
323
+ });
324
+ break;
325
+ }
326
+ }
327
+ const planEvent = events.filter((event) => event.type === "room/plan-state").at(-1);
328
+ const plan = planEvent?.type === "room/plan-state" ? (({ requests: _requests, ...view }) => view)(planEvent.data) : void 0;
329
+ const deliveries = replayDeliveries(events);
330
+ return {
331
+ members,
332
+ relays,
333
+ tasks,
334
+ ...plan === void 0 ? {} : { plan },
335
+ ...deliveries.length === 0 ? {} : { deliveries },
336
+ runs: [...runs.values()],
337
+ ...coordinator === void 0 ? {} : { coordinator },
338
+ ...goal === void 0 ? {} : { goal }
339
+ };
340
+ }
341
+ /**
342
+ * The member's dispatch cursor as of JUST BEFORE a given dispatch event: the
343
+ * latest earlier dispatch naming them. The engine reads the role-instruction
344
+ * carry against this cursor (pass {@link Number.MAX_SAFE_INTEGER} for a
345
+ * dispatch that is not itself journaled, e.g. a relay delivery).
346
+ * @param events - the session's event log.
347
+ * @param member - the dispatch target.
348
+ * @param beforeSeq - the current dispatch event's seq.
349
+ * @returns the previous dispatch seq, or undefined on the member's first dispatch.
350
+ */
351
+ function previousCursor(events, member, beforeSeq) {
352
+ let cursor;
353
+ for (const event of events) {
354
+ if (event.type !== "room/dispatch" || event.seq >= beforeSeq) continue;
355
+ if (event.data.targets.includes(member)) cursor = event.seq;
356
+ }
357
+ return cursor;
358
+ }
359
+ /**
360
+ * Whether the roster a member was last told about is stale: any roster-visible
361
+ * change with a seq past their dispatch cursor — a member-added or
362
+ * member-removed, or a member-updated that touches what the roster section
363
+ * shows (instructions = the one-line role, rename = the addressing name). A
364
+ * childSessionId/cwd/model-only update is NOT roster-visible (the delegation
365
+ * handle is journaled right after a first run; counting it would re-send the
366
+ * roster on every member's next dispatch for nothing — the model intent never
367
+ * enters any prompt either). A member with no cursor has
368
+ * never seen the roster, so their first dispatch always counts as stale.
369
+ * @param events - the session's event log.
370
+ * @param cursor - the member's pre-dispatch cursor ({@link previousCursor}).
371
+ * @returns true when the next dispatch must carry the roster section.
372
+ */
373
+ function rosterStaleSince(events, cursor) {
374
+ if (cursor === void 0) return true;
375
+ for (const event of events) {
376
+ if (event.seq <= cursor) continue;
377
+ if (event.type === "room/member-added" || event.type === "room/member-removed") return true;
378
+ if (event.type === "room/member-updated" && (event.data.instructions !== void 0 || event.data.rename !== void 0)) return true;
379
+ }
380
+ return false;
381
+ }
382
+ /**
383
+ * The role-instructions carry for the next dispatch to a member: 'initial'
384
+ * when the member has never been dispatched (their instructions open the
385
+ * first prompt), 'update' when the latest member-added/member-updated event
386
+ * naming them postdates their dispatch cursor (an edit rides the next
387
+ * dispatch as a context update). Instructions events older than the cursor
388
+ * were already carried.
389
+ * @param events - the session's event log.
390
+ * @param member - the dispatch target.
391
+ * @param cursor - the member's pre-dispatch cursor ({@link previousCursor}).
392
+ * @returns the carry, or undefined when nothing needs carrying.
393
+ */
394
+ function pendingInstructions(events, member, cursor) {
395
+ let lastSeq;
396
+ let lastInstructions;
397
+ for (const event of events) if (event.type === "room/member-added" && event.data.name === member && event.data.instructions !== void 0) {
398
+ lastSeq = event.seq;
399
+ lastInstructions = event.data.instructions;
400
+ } else if (event.type === "room/member-updated" && event.data.name === member) {
401
+ if (event.data.instructions === null) {
402
+ lastSeq = void 0;
403
+ lastInstructions = void 0;
404
+ } else if (event.data.instructions !== void 0) {
405
+ lastSeq = event.seq;
406
+ lastInstructions = event.data.instructions;
407
+ }
408
+ }
409
+ if (lastSeq === void 0 || lastInstructions === void 0) return void 0;
410
+ if (cursor === void 0) return {
411
+ kind: "initial",
412
+ instructions: lastInstructions
413
+ };
414
+ if (lastSeq > cursor) return {
415
+ kind: "update",
416
+ instructions: lastInstructions
417
+ };
418
+ }
419
+ /** Resolve legacy join identity through renames; never key a new dispatch by display name. */
420
+ function memberId(events, member) {
421
+ if (member.id !== void 0) return member.id;
422
+ let name = member.name;
423
+ for (let index = events.length - 1; index >= 0; index--) {
424
+ const event = events[index];
425
+ if (event.type === "room/member-updated" && event.data.rename === name) name = event.data.name;
426
+ if (event.type === "room/member-added" && event.data.name === name) return event.data.id ?? `legacy:${event.seq}`;
427
+ }
428
+ throw new Error("Room member has no join identity");
429
+ }
430
+ function coordinatorMember(state, events) {
431
+ if (state.coordinator === void 0) return state.members.find((member) => member.kind === "main-agent");
432
+ return state.members.find((member) => (member.id ?? (events === void 0 ? void 0 : memberId(events, member))) === state.coordinator.memberId);
433
+ }
434
+ //#endregion
435
+ export { memberId as a, pendingInstructions as c, rosterStaleSince as d, replayDeliveries as f, isRoomLog as i, previousCursor as l, ROOM_EVENT_TYPES as n, parseMentions as o, coordinatorMember as r, parseRelayDirective as s, MAIN_AGENT_MEMBER as t, replay as u };