@agentshouse/mdruntime 0.1.0-c8eac46931574 → 0.1.0-c8f07dbee1c62

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 (260) hide show
  1. package/README.md +1 -1
  2. package/dist/acceptance.d.ts +6 -0
  3. package/dist/acceptance.d.ts.map +1 -0
  4. package/dist/acceptance.js +389 -0
  5. package/dist/actions.d.ts +20 -0
  6. package/dist/actions.d.ts.map +1 -0
  7. package/dist/actions.js +250 -0
  8. package/dist/adapter.d.ts +23 -25
  9. package/dist/adapter.d.ts.map +1 -1
  10. package/dist/attached-model.d.ts +3 -10
  11. package/dist/attached-model.d.ts.map +1 -1
  12. package/dist/attached-model.js +21 -56
  13. package/dist/bootstrap.d.ts +8 -4
  14. package/dist/bootstrap.d.ts.map +1 -1
  15. package/dist/bootstrap.js +209 -78
  16. package/dist/cell.d.ts +6 -0
  17. package/dist/cell.d.ts.map +1 -0
  18. package/dist/cell.js +9 -0
  19. package/dist/chain.d.ts +2 -1
  20. package/dist/chain.d.ts.map +1 -1
  21. package/dist/change.d.ts +1 -0
  22. package/dist/change.d.ts.map +1 -1
  23. package/dist/chat-types.d.ts +5 -0
  24. package/dist/chat-types.d.ts.map +1 -0
  25. package/dist/chat-types.js +13 -0
  26. package/dist/client.d.ts +17 -2
  27. package/dist/client.d.ts.map +1 -1
  28. package/dist/client.js +3 -0
  29. package/dist/collection.d.ts +2 -1
  30. package/dist/collection.d.ts.map +1 -1
  31. package/dist/compile.d.ts +7 -7
  32. package/dist/compile.d.ts.map +1 -1
  33. package/dist/compile.js +229 -301
  34. package/dist/connection.d.ts +5 -141
  35. package/dist/connection.d.ts.map +1 -1
  36. package/dist/connection.js +98 -5279
  37. package/dist/context.d.ts +2 -0
  38. package/dist/context.d.ts.map +1 -0
  39. package/dist/context.js +7 -0
  40. package/dist/conversation-row.d.ts +33 -0
  41. package/dist/conversation-row.d.ts.map +1 -0
  42. package/dist/conversation-row.js +53 -0
  43. package/dist/conversation.d.ts +16 -6
  44. package/dist/conversation.d.ts.map +1 -1
  45. package/dist/conversation.js +364 -212
  46. package/dist/discovery.d.ts +10 -0
  47. package/dist/discovery.d.ts.map +1 -0
  48. package/dist/discovery.js +175 -0
  49. package/dist/durability.d.ts +15 -0
  50. package/dist/durability.d.ts.map +1 -1
  51. package/dist/entry.d.ts +2 -2
  52. package/dist/entry.d.ts.map +1 -1
  53. package/dist/entry.js +17 -8
  54. package/dist/figures.d.ts +48 -0
  55. package/dist/figures.d.ts.map +1 -0
  56. package/dist/figures.js +197 -0
  57. package/dist/holdings.d.ts +29 -0
  58. package/dist/holdings.d.ts.map +1 -0
  59. package/dist/holdings.js +95 -0
  60. package/dist/host.d.ts +2 -2
  61. package/dist/host.d.ts.map +1 -1
  62. package/dist/host.js +13 -13
  63. package/dist/identity.d.ts +0 -1
  64. package/dist/identity.d.ts.map +1 -1
  65. package/dist/identity.js +0 -3
  66. package/dist/index.d.ts +19 -5
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +4 -1
  69. package/dist/install.d.ts +56 -0
  70. package/dist/install.d.ts.map +1 -0
  71. package/dist/install.js +560 -0
  72. package/dist/job.d.ts +26 -1
  73. package/dist/job.d.ts.map +1 -1
  74. package/dist/job.js +41 -0
  75. package/dist/lifecycle.d.ts +7 -0
  76. package/dist/lifecycle.d.ts.map +1 -0
  77. package/dist/lifecycle.js +267 -0
  78. package/dist/listing.d.ts +53 -0
  79. package/dist/listing.d.ts.map +1 -0
  80. package/dist/listing.js +175 -0
  81. package/dist/live.d.ts +4 -4
  82. package/dist/live.d.ts.map +1 -1
  83. package/dist/live.js +53 -15
  84. package/dist/locks.d.ts +9 -0
  85. package/dist/locks.d.ts.map +1 -0
  86. package/dist/locks.js +61 -0
  87. package/dist/log.d.ts +1 -0
  88. package/dist/log.d.ts.map +1 -1
  89. package/dist/lost.d.ts +5 -0
  90. package/dist/lost.d.ts.map +1 -0
  91. package/dist/lost.js +61 -0
  92. package/dist/memory.d.ts +4 -1
  93. package/dist/memory.d.ts.map +1 -1
  94. package/dist/memory.js +43 -4
  95. package/dist/messages.d.ts +25 -1
  96. package/dist/messages.d.ts.map +1 -1
  97. package/dist/messages.js +84 -5
  98. package/dist/model.d.ts +2 -9
  99. package/dist/model.d.ts.map +1 -1
  100. package/dist/model.js +0 -9
  101. package/dist/models.d.ts +14 -0
  102. package/dist/models.d.ts.map +1 -0
  103. package/dist/models.js +193 -0
  104. package/dist/opening.d.ts +4 -0
  105. package/dist/opening.d.ts.map +1 -0
  106. package/dist/opening.js +56 -0
  107. package/dist/outcome.d.ts +4 -1
  108. package/dist/outcome.d.ts.map +1 -1
  109. package/dist/outcome.js +3 -0
  110. package/dist/overlay.d.ts +1 -0
  111. package/dist/overlay.d.ts.map +1 -1
  112. package/dist/overlay.js +11 -0
  113. package/dist/parts.d.ts +59 -0
  114. package/dist/parts.d.ts.map +1 -0
  115. package/dist/parts.js +118 -0
  116. package/dist/preparation.d.ts +20 -0
  117. package/dist/preparation.d.ts.map +1 -0
  118. package/dist/preparation.js +226 -0
  119. package/dist/queries.d.ts +16 -0
  120. package/dist/queries.d.ts.map +1 -0
  121. package/dist/queries.js +272 -0
  122. package/dist/query.d.ts +13 -212
  123. package/dist/query.d.ts.map +1 -1
  124. package/dist/query.js +3 -524
  125. package/dist/questions.d.ts +34 -0
  126. package/dist/questions.d.ts.map +1 -0
  127. package/dist/questions.js +122 -0
  128. package/dist/reach.d.ts +9 -8
  129. package/dist/reach.d.ts.map +1 -1
  130. package/dist/reach.js +160 -107
  131. package/dist/react.d.ts +9 -2
  132. package/dist/react.d.ts.map +1 -1
  133. package/dist/read.d.ts +7 -0
  134. package/dist/read.d.ts.map +1 -0
  135. package/dist/read.js +17 -0
  136. package/dist/reads.d.ts +36 -0
  137. package/dist/reads.d.ts.map +1 -0
  138. package/dist/reads.js +150 -0
  139. package/dist/recovery.d.ts.map +1 -1
  140. package/dist/recovery.js +9 -1
  141. package/dist/refresh.d.ts +1 -1
  142. package/dist/refresh.d.ts.map +1 -1
  143. package/dist/refresh.js +8 -20
  144. package/dist/refusal.d.ts +5 -0
  145. package/dist/refusal.d.ts.map +1 -0
  146. package/dist/refusal.js +56 -0
  147. package/dist/replica.d.ts +8 -8
  148. package/dist/replica.d.ts.map +1 -1
  149. package/dist/replica.js +52 -19
  150. package/dist/room-draft.d.ts +2 -2
  151. package/dist/room-draft.d.ts.map +1 -1
  152. package/dist/room-drafts.d.ts +24 -0
  153. package/dist/room-drafts.d.ts.map +1 -0
  154. package/dist/room-drafts.js +216 -0
  155. package/dist/rows.d.ts +14 -22
  156. package/dist/rows.d.ts.map +1 -1
  157. package/dist/rows.js +48 -189
  158. package/dist/runtime.d.ts +42 -0
  159. package/dist/runtime.d.ts.map +1 -0
  160. package/dist/runtime.js +1 -0
  161. package/dist/save.d.ts.map +1 -1
  162. package/dist/save.js +13 -10
  163. package/dist/saves.d.ts +70 -0
  164. package/dist/saves.d.ts.map +1 -0
  165. package/dist/saves.js +1004 -0
  166. package/dist/search.d.ts.map +1 -1
  167. package/dist/search.js +1 -1
  168. package/dist/seed.d.ts +11 -2
  169. package/dist/seed.d.ts.map +1 -1
  170. package/dist/seed.js +71 -0
  171. package/dist/shared.d.ts +1 -1
  172. package/dist/shared.d.ts.map +1 -1
  173. package/dist/shared.js +9 -5
  174. package/dist/shown.d.ts +31 -0
  175. package/dist/shown.d.ts.map +1 -0
  176. package/dist/shown.js +294 -0
  177. package/dist/socket/admission.d.ts +7 -0
  178. package/dist/socket/admission.d.ts.map +1 -0
  179. package/dist/socket/admission.js +39 -0
  180. package/dist/socket/answers.d.ts +6 -0
  181. package/dist/socket/answers.d.ts.map +1 -0
  182. package/dist/socket/answers.js +20 -0
  183. package/dist/socket/lanes.d.ts +20 -0
  184. package/dist/socket/lanes.d.ts.map +1 -0
  185. package/dist/socket/lanes.js +77 -0
  186. package/dist/socket/reads.d.ts +10 -0
  187. package/dist/socket/reads.d.ts.map +1 -0
  188. package/dist/socket/reads.js +31 -0
  189. package/dist/socket/rooms.d.ts +43 -0
  190. package/dist/socket/rooms.d.ts.map +1 -0
  191. package/dist/socket/rooms.js +90 -0
  192. package/dist/socket/session.d.ts +39 -0
  193. package/dist/socket/session.d.ts.map +1 -0
  194. package/dist/socket/session.js +23 -0
  195. package/dist/socket.d.ts +70 -0
  196. package/dist/socket.d.ts.map +1 -0
  197. package/dist/socket.js +175 -0
  198. package/dist/source.d.ts +61 -0
  199. package/dist/source.d.ts.map +1 -0
  200. package/dist/source.js +81 -0
  201. package/dist/status.d.ts +0 -2
  202. package/dist/status.d.ts.map +1 -1
  203. package/dist/status.js +0 -6
  204. package/dist/store/fences.d.ts +16 -0
  205. package/dist/store/fences.d.ts.map +1 -0
  206. package/dist/store/fences.js +26 -0
  207. package/dist/store/installs.d.ts +30 -0
  208. package/dist/store/installs.d.ts.map +1 -0
  209. package/dist/store/installs.js +34 -0
  210. package/dist/store/readiness.d.ts +10 -0
  211. package/dist/store/readiness.d.ts.map +1 -0
  212. package/dist/store/readiness.js +52 -0
  213. package/dist/store/records.d.ts +212 -0
  214. package/dist/store/records.d.ts.map +1 -0
  215. package/dist/store/records.js +408 -0
  216. package/dist/store/room-held.d.ts +8 -0
  217. package/dist/store/room-held.d.ts.map +1 -0
  218. package/dist/store/room-held.js +17 -0
  219. package/dist/store/space.d.ts +11 -0
  220. package/dist/store/space.d.ts.map +1 -0
  221. package/dist/store/space.js +43 -0
  222. package/dist/store/watchers.d.ts +17 -0
  223. package/dist/store/watchers.d.ts.map +1 -0
  224. package/dist/store/watchers.js +57 -0
  225. package/dist/store/writes.d.ts +24 -0
  226. package/dist/store/writes.d.ts.map +1 -0
  227. package/dist/store/writes.js +125 -0
  228. package/dist/store.d.ts +107 -153
  229. package/dist/store.d.ts.map +1 -1
  230. package/dist/store.js +143 -396
  231. package/dist/stored.d.ts +58 -64
  232. package/dist/stored.d.ts.map +1 -1
  233. package/dist/stored.js +244 -156
  234. package/dist/surface.d.ts +88 -0
  235. package/dist/surface.d.ts.map +1 -0
  236. package/dist/surface.js +222 -0
  237. package/dist/sync.d.ts +20 -0
  238. package/dist/sync.d.ts.map +1 -0
  239. package/dist/sync.js +531 -0
  240. package/dist/tables.d.ts +111 -49
  241. package/dist/tables.d.ts.map +1 -1
  242. package/dist/tables.js +209 -54
  243. package/dist/tallies.d.ts +18 -0
  244. package/dist/tallies.d.ts.map +1 -0
  245. package/dist/tallies.js +129 -0
  246. package/dist/validation.d.ts.map +1 -1
  247. package/dist/validation.js +7 -4
  248. package/dist/windows.d.ts +1 -12
  249. package/dist/windows.d.ts.map +1 -1
  250. package/dist/windows.js +2 -75
  251. package/dist/wire.d.ts +16 -1
  252. package/dist/wire.d.ts.map +1 -1
  253. package/dist/wire.js +6 -0
  254. package/package.json +2 -2
  255. package/dist/fence.d.ts +0 -6
  256. package/dist/fence.d.ts.map +0 -1
  257. package/dist/fence.js +0 -10
  258. package/dist/phrase.d.ts +0 -3
  259. package/dist/phrase.d.ts.map +0 -1
  260. package/dist/phrase.js +0 -8
@@ -1,10 +1,14 @@
1
+ import { cursorOf, decodedCursor } from '@agentshouse/mdmodel';
1
2
  import { conversationChangeSet } from './change-set.js';
3
+ import { heldFacts } from './conversation-row.js';
2
4
  import { runtimeError } from './failure.js';
3
5
  import { refusedForSpace } from './durability.js';
4
6
  import { changeBatchIdentity, chatDraftRevision } from './identity.js';
5
7
  import { OFFLINE } from './outcome.js';
8
+ import { jobsOf, runningLine } from './parts.js';
9
+ import { openQuestion, questionAsked } from './questions.js';
6
10
  import { untilAnswered } from './recovery.js';
7
- import { capped, cursorOf, decodedCursor, FROM_START, messageOrder, precedes } from './windows.js';
11
+ import { capped, FROM_START, messageOrder, precedes } from './windows.js';
8
12
  const QUIET = {
9
13
  turn: null,
10
14
  questions: [],
@@ -33,6 +37,7 @@ export function unheldView(conversation) {
33
37
  turn: null,
34
38
  questions: [],
35
39
  process: null,
40
+ processEnded: null,
36
41
  commands: [],
37
42
  options: [],
38
43
  starting: null,
@@ -40,11 +45,14 @@ export function unheldView(conversation) {
40
45
  link: null,
41
46
  deliveryUnknown: [],
42
47
  chatDraft: null,
48
+ jobs: [],
43
49
  };
44
50
  }
45
51
  export const SESSION_STARTED = 'Session started';
46
52
  const NEW_CHAT = 'new:';
47
53
  export const HISTORY_CONVERSATIONS = 64;
54
+ // A row is read at a glance and each telling clones the list to every observing tab, while a running line changes at each Kit flush.
55
+ const ROW_INTERVAL_MS = 1_000;
48
56
  export function newChat(agent) {
49
57
  return `${NEW_CHAT}${agent}`;
50
58
  }
@@ -219,7 +227,6 @@ function phraseOf(text) {
219
227
  }
220
228
  return text;
221
229
  }
222
- const EXPIRED = { kind: 'rejected', code: 'expired', fields: {}, retryable: false };
223
230
  const DURABLE_WRITE = {
224
231
  kind: 'rejected',
225
232
  code: 'durable-write',
@@ -253,10 +260,6 @@ function returned(draft, submission, others) {
253
260
  }, others);
254
261
  }
255
262
  export function createConversationStore(deps) {
256
- const stored = new Set();
257
- const reads = new Map();
258
- const partials = new Set();
259
- const drafts = new Map();
260
263
  const sending = new Map();
261
264
  const behind = new Set();
262
265
  const catching = new Map();
@@ -264,15 +267,16 @@ export function createConversationStore(deps) {
264
267
  const lives = new Map();
265
268
  const unsent = new Map();
266
269
  const views = new Map();
267
- const listing = new Map();
270
+ const heardNewest = new Map();
268
271
  let relisting = null;
269
- let listCursor = null;
270
272
  const creations = new Set();
271
273
  const listWatchers = new Set();
272
274
  const unlisted = new Set();
273
275
  let listKnown = false;
274
- let listHeld = false;
275
- let listKept = false;
276
+ let listOwed = false;
277
+ let listTelling = false;
278
+ let rowTimer = null;
279
+ const closing = new AbortController();
276
280
  const agentWatchers = new Set();
277
281
  const unnamed = new Set();
278
282
  let agentsShown = null;
@@ -283,8 +287,7 @@ export function createConversationStore(deps) {
283
287
  function liveOf(conversation) {
284
288
  return lives.get(conversation) ?? QUIET;
285
289
  }
286
- function viewOf(conversation, held, frame) {
287
- const draft = drafts.get(conversation);
290
+ function viewOf(conversation, held, frame, draft, ended, marked) {
288
291
  const facts = liveOf(conversation);
289
292
  return {
290
293
  conversation,
@@ -298,15 +301,17 @@ export function createConversationStore(deps) {
298
301
  turn: structuredClone(facts.turn),
299
302
  questions: structuredClone(facts.questions),
300
303
  process: facts.process === null ? null : { ...facts.process },
304
+ processEnded: ended,
301
305
  commands: structuredClone(facts.commands),
302
306
  options: structuredClone(facts.options),
303
307
  starting: facts.starting,
304
308
  subagents: structuredClone(facts.subagents),
305
309
  link: facts.link === null ? null : { ...facts.link },
306
310
  deliveryUnknown: [...facts.deliveryUnknown],
307
- chatDraft: draft === undefined || unwritten(draft)
308
- ? null
309
- : { text: draft.text, revision: draft.revision },
311
+ chatDraft: draft === null || unwritten(draft) ? null : { text: draft.text, revision: draft.revision },
312
+ jobs: facts.process?.running === false
313
+ ? []
314
+ : jobsOf(marked.filter(({ at }) => ended === null || at > ended), facts.turn),
310
315
  };
311
316
  }
312
317
  function emit(conversation) {
@@ -318,7 +323,7 @@ export function createConversationStore(deps) {
318
323
  const ready = [...view.watchers].filter((watcher) => watcher.ready);
319
324
  if (ready.length === 0)
320
325
  continue;
321
- const value = viewOf(conversation, view.window, sharedFrame(view, view.window));
326
+ const value = viewOf(conversation, view.window, sharedFrame(view, view.window), view.draft, view.ended, view.marked);
322
327
  for (const watcher of ready)
323
328
  watcher.notify(value);
324
329
  }
@@ -328,11 +333,13 @@ export function createConversationStore(deps) {
328
333
  }
329
334
  async function slide(local, conversation, arriving) {
330
335
  let count;
336
+ let marked;
331
337
  for (const view of views.values()) {
332
338
  if (view.conversation !== conversation || view.window === null)
333
339
  continue;
334
340
  if (arriving === null) {
335
341
  view.window = [];
342
+ view.marked = [];
336
343
  view.more = false;
337
344
  if (view.total !== null)
338
345
  view.total = 0;
@@ -342,6 +349,10 @@ export function createConversationStore(deps) {
342
349
  const next = windowOf(view.window, joining, view.asked.limit);
343
350
  view.window = next.messages;
344
351
  view.more ||= next.dropped;
352
+ if (arriving.length > 0) {
353
+ marked ??= await local.jobMarkers(conversation);
354
+ view.marked = marked;
355
+ }
345
356
  if (view.total !== null && arriving.length > 0) {
346
357
  count ??= await local.messageCount(conversation);
347
358
  view.total = count;
@@ -354,79 +365,179 @@ export function createConversationStore(deps) {
354
365
  return 'asking';
355
366
  return facts.turn === null ? 'idle' : 'working';
356
367
  }
357
- function entries() {
358
- return [...listing.values()].map((entry) => ({
368
+ function rowOf(conversation) {
369
+ const turn = lives.get(conversation)?.turn ?? null;
370
+ return {
371
+ state: stateOf(conversation),
372
+ started: turn?.started ?? null,
373
+ line: turn === null ? null : runningLine(turn.parts),
374
+ question: openQuestion(liveOf(conversation).questions),
375
+ asked: questionAsked(liveOf(conversation).questions),
376
+ };
377
+ }
378
+ async function entries() {
379
+ const list = await heldList();
380
+ if (list === null)
381
+ return null;
382
+ const held = list.length === 0
383
+ ? []
384
+ : await deps.durably((local) => local.conversationRows(list.map(({ conversation }) => conversation)));
385
+ const rows = new Map(held.map((one) => [one.conversation, one]));
386
+ return list.map((entry) => ({
359
387
  conversation: entry.conversation,
360
- agent: { identity: entry.agent, name: entry.agentName },
388
+ agent: { identity: entry.agent, name: entry.agentName, retired: entry.agentRetired },
361
389
  name: entry.name,
362
390
  created: entry.created,
363
391
  finished: entry.finished,
364
392
  outcome: entry.outcome,
365
393
  waiting: entry.waiting,
366
- state: stateOf(entry.conversation),
367
- started: lives.get(entry.conversation)?.turn?.started ?? null,
394
+ ...rowOf(entry.conversation),
395
+ ...heldFacts(rows.get(entry.conversation), entry),
368
396
  }));
369
397
  }
370
- function keepList() {
371
- if (!listKnown || listKept)
372
- return;
373
- listKept = true;
374
- queueMicrotask(() => {
375
- listKept = false;
376
- if (deps.closed())
398
+ async function heldList() {
399
+ const stored = (await (await deps.local()).facts()).conversationList;
400
+ if (stored !== null)
401
+ return stored;
402
+ return !listKnown && deps.stream() !== undefined && deps.online() ? null : [];
403
+ }
404
+ async function endedIn(conversation) {
405
+ const list = await heldList();
406
+ return list?.find((entry) => entry.conversation === conversation)?.processEnded ?? null;
407
+ }
408
+ function listChanged(change) {
409
+ void deps
410
+ .durably(async (local) => {
411
+ const { conversationList, conversationCursor } = await local.facts();
412
+ const kept = change.replacing ? [] : (conversationList ?? []);
413
+ const held = new Map(kept.map((entry) => [entry.conversation, entry]));
414
+ for (const entry of change.set) {
415
+ held.set(entry.conversation, { ...entry });
416
+ creations.delete(entry.conversation);
417
+ }
418
+ if (change.gone !== null)
419
+ held.delete(change.gone);
420
+ const list = [...held.values()];
421
+ if (JSON.stringify(list) !== JSON.stringify(conversationList)) {
422
+ await local.fact('conversationList', list);
423
+ }
424
+ if (change.cursor !== undefined) {
425
+ if (change.cursor !== conversationCursor) {
426
+ await local.fact('conversationCursor', change.cursor);
427
+ }
428
+ deps.stream()?.hold(change.cursor);
429
+ }
430
+ return list;
431
+ })
432
+ .catch(async () => (await (await deps.local()).facts()).conversationList ?? [])
433
+ .then((list) => {
434
+ if (deps.closed() || change.paged === true)
377
435
  return;
378
- const entries = [...listing.values()];
379
- const cursor = listCursor;
380
- void deps
381
- .durably(async (local) => {
382
- await local.fact('conversationList', entries);
383
- await local.fact('conversationCursor', cursor);
384
- })
385
- .catch(() => { });
436
+ endedAnew(list);
437
+ if (change.cursor !== undefined) {
438
+ listKnown = true;
439
+ settleBehind(list);
440
+ openingEnded();
441
+ deps.settled();
442
+ }
443
+ emitList();
444
+ if (!listKnown)
445
+ return;
446
+ retiredDrafts(list);
447
+ if (change.cursor === undefined &&
448
+ (list.length !== replicated.size ||
449
+ list.some(({ conversation }) => !replicated.has(conversation)))) {
450
+ deps.again();
451
+ }
452
+ }, () => { });
453
+ }
454
+ function endedAnew(list) {
455
+ const entries = new Map(list.map((entry) => [entry.conversation, entry]));
456
+ for (const view of views.values()) {
457
+ const ended = entries.get(view.conversation)?.processEnded ?? null;
458
+ if (view.window === null || ended === view.ended)
459
+ continue;
460
+ view.ended = ended;
461
+ emit(view.conversation);
462
+ }
463
+ }
464
+ let listShown = Promise.resolve();
465
+ function shownList(work) {
466
+ const reading = heldList();
467
+ listShown = listShown.then(async () => {
468
+ const list = await reading.catch(() => null);
469
+ if (list === null || deps.closed())
470
+ return;
471
+ work(list);
386
472
  });
387
473
  }
388
- function listed(entry) {
389
- listing.set(entry.conversation, { ...entry });
390
- creations.delete(entry.conversation);
474
+ async function told() {
475
+ try {
476
+ while (listOwed && !deps.closed()) {
477
+ listOwed = false;
478
+ const value = await untilAnswered(entries, closing.signal);
479
+ if (deps.closed())
480
+ return;
481
+ if (value === null)
482
+ continue;
483
+ for (const watcher of listWatchers)
484
+ watcher(value);
485
+ for (const watcher of [...unlisted]) {
486
+ unlisted.delete(watcher);
487
+ listWatchers.add(watcher);
488
+ watcher(value);
489
+ }
490
+ }
491
+ }
492
+ finally {
493
+ listTelling = false;
494
+ }
495
+ }
496
+ function tellList() {
497
+ if (deps.closed() || (listWatchers.size === 0 && unlisted.size === 0))
498
+ return;
499
+ listOwed = true;
500
+ if (listTelling)
501
+ return;
502
+ listTelling = true;
503
+ void told().catch(() => { });
504
+ }
505
+ function tellSoon() {
506
+ if (rowTimer !== null)
507
+ return;
508
+ rowTimer = setTimeout(() => {
509
+ rowTimer = null;
510
+ tellList();
511
+ }, ROW_INTERVAL_MS);
391
512
  }
392
513
  function emitList() {
393
514
  if (deps.closed())
394
515
  return;
395
- if (listWatchers.size > 0) {
396
- const value = entries();
397
- for (const watcher of listWatchers)
398
- watcher(value);
399
- }
516
+ tellList();
400
517
  emitAgents();
401
518
  learned();
402
519
  }
403
520
  function learned() {
404
521
  if (deps.closed())
405
522
  return;
406
- if (!listKnown && !listHeld && deps.stream() !== undefined && deps.online())
523
+ if (unlisted.size > 0)
524
+ tellList();
525
+ if (unnamed.size === 0)
407
526
  return;
408
- if (unlisted.size > 0) {
409
- const value = entries();
410
- for (const watcher of [...unlisted]) {
411
- unlisted.delete(watcher);
412
- listWatchers.add(watcher);
413
- watcher(value);
414
- }
415
- }
416
- if (unnamed.size > 0) {
417
- const value = agents();
527
+ shownList((list) => {
528
+ const value = agents(list);
418
529
  for (const watcher of [...unnamed]) {
419
530
  unnamed.delete(watcher);
420
531
  agentWatchers.add(watcher);
421
532
  watcher(value);
422
533
  }
423
- }
534
+ });
424
535
  }
425
- function agents() {
536
+ function agents(list) {
426
537
  const named = new Map();
427
538
  for (const agent of listedAgents ?? [])
428
539
  named.set(agent.agent, agent);
429
- for (const entry of listing.values()) {
540
+ for (const entry of list) {
430
541
  named.set(entry.agent, { name: entry.agentName, retired: entry.agentRetired });
431
542
  }
432
543
  return [...named]
@@ -456,31 +567,35 @@ export function createConversationStore(deps) {
456
567
  function emitAgents() {
457
568
  if (deps.closed() || agentWatchers.size === 0)
458
569
  return;
459
- const value = agents();
460
- const shown = JSON.stringify(value);
461
- if (shown === agentsShown)
462
- return;
463
- agentsShown = shown;
464
- for (const watcher of agentWatchers)
465
- watcher(value);
570
+ shownList((list) => {
571
+ const value = agents(list);
572
+ const shown = JSON.stringify(value);
573
+ if (shown === agentsShown)
574
+ return;
575
+ agentsShown = shown;
576
+ for (const watcher of agentWatchers)
577
+ watcher(value);
578
+ });
466
579
  }
467
580
  function setLive(conversation, next) {
468
- const before = stateOf(conversation);
469
- const began = lives.get(conversation)?.turn?.started;
581
+ const before = rowOf(conversation);
470
582
  if (next === null)
471
583
  lives.delete(conversation);
472
584
  else
473
585
  lives.set(conversation, next);
474
- if (!listing.has(conversation))
475
- return;
476
- if (stateOf(conversation) !== before || next?.turn?.started !== began)
586
+ const after = rowOf(conversation);
587
+ if (after.state !== before.state || after.started !== before.started)
477
588
  emitList();
589
+ else if (after.line !== before.line ||
590
+ after.question !== before.question ||
591
+ after.asked !== before.asked)
592
+ tellSoon();
478
593
  }
479
- function retiredChat(conversation) {
594
+ function retiredChat(conversation, list) {
480
595
  if (!conversation.startsWith(NEW_CHAT))
481
596
  return false;
482
597
  const agent = conversation.slice(NEW_CHAT.length);
483
- return [...listing.values()].some((entry) => entry.agent === agent && entry.agentRetired);
598
+ return list.some((entry) => entry.agent === agent && entry.agentRetired);
484
599
  }
485
600
  function live(conversation, change) {
486
601
  const owed = unsent.get(conversation);
@@ -538,17 +653,23 @@ export function createConversationStore(deps) {
538
653
  }
539
654
  async function remove(leaving) {
540
655
  await deps.durably(async (local) => {
656
+ const kept = new Set([
657
+ ...(await local.conversations(leaving)).map(({ conversation }) => conversation),
658
+ ...(await local.chatDrafts())
659
+ .filter(({ conversation }) => leaving.includes(conversation))
660
+ .map(({ conversation }) => conversation),
661
+ ]);
541
662
  await local.dropConversations(leaving);
542
663
  const changed = [];
543
664
  for (const conversation of leaving) {
544
- if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
665
+ if (kept.has(conversation) || lives.has(conversation))
545
666
  changed.push(conversation);
667
+ for (const view of views.values()) {
668
+ if (view.conversation === conversation)
669
+ view.draft = null;
546
670
  }
547
- stored.delete(conversation);
548
- reads.delete(conversation);
549
- partials.delete(conversation);
550
- drafts.delete(conversation);
551
671
  behind.delete(conversation);
672
+ heardNewest.delete(conversation);
552
673
  unsent.delete(conversation);
553
674
  setLive(conversation, null);
554
675
  await slide(local, conversation, null);
@@ -557,19 +678,68 @@ export function createConversationStore(deps) {
557
678
  deps.publish(conversationChangeSet(changed));
558
679
  });
559
680
  }
560
- function missed(conversation) {
561
- const read = reads.get(conversation);
562
- const newest = listing.get(conversation)?.newest;
681
+ function newestOfListed(entry) {
682
+ if (entry === undefined)
683
+ return undefined;
684
+ const heard = heardNewest.get(entry.conversation);
685
+ if (heard === undefined)
686
+ return entry.newest;
687
+ return entry.newest === null || heard > entry.newest ? heard : entry.newest;
688
+ }
689
+ function missed(conversation, held, entry) {
690
+ const read = held?.read;
691
+ const newest = newestOfListed(entry);
563
692
  return (read === undefined ||
564
693
  newest === undefined ||
565
- partials.has(conversation) ||
694
+ held?.partial === true ||
566
695
  (newest !== null && (read === null || newest > read)));
567
696
  }
568
- function noted(conversation, message) {
569
- const entry = listing.get(conversation);
570
- if (entry === undefined || (entry.newest !== null && entry.newest >= message.position))
697
+ async function readsOf(local, conversations) {
698
+ return new Map((await local.conversations(conversations)).map((held) => [held.conversation, held]));
699
+ }
700
+ function retiredDrafts(list) {
701
+ if (!list.some((entry) => entry.agentRetired))
571
702
  return;
572
- listing.set(conversation, { ...entry, newest: message.position });
703
+ void deps
704
+ .local()
705
+ .then((local) => local.chatDrafts())
706
+ .then((held) => {
707
+ if (!deps.closed() && held.some(({ conversation }) => retiredChat(conversation, list))) {
708
+ deps.again();
709
+ }
710
+ }, () => { });
711
+ }
712
+ function settleBehind(list) {
713
+ void deps
714
+ .durably(async (local) => {
715
+ const entries = new Map(list.map((entry) => [entry.conversation, entry]));
716
+ const named = [...replicated].filter((conversation) => entries.has(conversation));
717
+ const held = await readsOf(local, named);
718
+ let owed = false;
719
+ for (const conversation of named) {
720
+ if (!missed(conversation, held.get(conversation), entries.get(conversation))) {
721
+ behind.delete(conversation);
722
+ }
723
+ else if (!behind.has(conversation)) {
724
+ gap(conversation);
725
+ owed = true;
726
+ }
727
+ }
728
+ return owed;
729
+ })
730
+ .then((owed) => {
731
+ if (deps.closed())
732
+ return;
733
+ deps.settled();
734
+ if (owed)
735
+ deps.again();
736
+ }, () => { });
737
+ }
738
+ function noted(conversation, message) {
739
+ const heard = heardNewest.get(conversation);
740
+ if (heard === undefined || message.position > heard) {
741
+ heardNewest.set(conversation, message.position);
742
+ }
573
743
  }
574
744
  function gap(conversation) {
575
745
  gaps.set(conversation, (gaps.get(conversation) ?? 0) + 1);
@@ -579,7 +749,7 @@ export function createConversationStore(deps) {
579
749
  return deps.durably(async (local) => {
580
750
  if (!replicated.has(conversation))
581
751
  return;
582
- const read = reads.get(conversation);
752
+ const read = (await readsOf(local, [conversation])).get(conversation)?.read;
583
753
  const advances = read !== undefined &&
584
754
  !behind.has(conversation) &&
585
755
  (read === null || message.position > read);
@@ -589,19 +759,18 @@ export function createConversationStore(deps) {
589
759
  messages: [message],
590
760
  ...(advances ? { read: { at: message.position, partial: false } } : {}),
591
761
  });
592
- stored.add(conversation);
593
- if (advances)
594
- reads.set(conversation, message.position);
595
762
  sealTurns(conversation, new Set([message.turn]));
596
763
  await slide(local, conversation, [message]);
597
764
  deps.publish(conversationChangeSet([conversation]));
765
+ tellList();
598
766
  });
599
767
  }
600
768
  async function readHistories(conversations, ended) {
601
769
  const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
770
+ const held = await readsOf(await deps.local(), conversations);
602
771
  let waiting = conversations.map((conversation) => ({
603
772
  conversation,
604
- after: reads.get(conversation) ?? undefined,
773
+ after: held.get(conversation)?.read ?? undefined,
605
774
  }));
606
775
  while (waiting.length > 0) {
607
776
  const asking = waiting.slice(0, HISTORY_CONVERSATIONS);
@@ -657,15 +826,10 @@ export function createConversationStore(deps) {
657
826
  return;
658
827
  const whole = next === undefined;
659
828
  const cursor = next ?? newestOf(arriving, from);
660
- const partial = !whole && (partials.has(conversation) || !reads.has(conversation));
829
+ const held = (await readsOf(local, [conversation])).get(conversation);
830
+ const partial = !whole && (held?.partial === true || held?.read === undefined);
661
831
  const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
662
832
  await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
663
- stored.add(conversation);
664
- reads.set(conversation, cursor);
665
- if (partial)
666
- partials.add(conversation);
667
- else
668
- partials.delete(conversation);
669
833
  sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
670
834
  if (whole && (gaps.get(conversation) ?? 0) === generation)
671
835
  behind.delete(conversation);
@@ -673,6 +837,8 @@ export function createConversationStore(deps) {
673
837
  if (arriving.length > 0 || sealing || whole) {
674
838
  deps.publish(conversationChangeSet([conversation]));
675
839
  }
840
+ if (arriving.length > 0)
841
+ tellSoon();
676
842
  });
677
843
  }
678
844
  function heard(event) {
@@ -739,17 +905,24 @@ export function createConversationStore(deps) {
739
905
  emit(conversation);
740
906
  }
741
907
  async function kept(local, conversation, following) {
742
- if (following === null || empty(following)) {
908
+ const shown = following === null || empty(following) ? null : following;
909
+ if (shown === null)
743
910
  await local.dropChatDraft(conversation);
744
- drafts.delete(conversation);
745
- }
746
- else {
747
- await local.keepChatDrafts([following]);
748
- drafts.set(conversation, following);
911
+ else
912
+ await local.keepChatDrafts([shown]);
913
+ shownDraft(conversation, shown);
914
+ }
915
+ function shownDraft(conversation, draft) {
916
+ for (const view of views.values()) {
917
+ if (view.conversation === conversation)
918
+ view.draft = draft;
749
919
  }
750
920
  }
921
+ async function draftAt(local, conversation) {
922
+ return (await local.chatDraft(conversation)) ?? undefined;
923
+ }
751
924
  async function sealed(local, conversation, turns) {
752
- const held = drafts.get(conversation);
925
+ const held = await draftAt(local, conversation);
753
926
  const submitted = held?.submitted ?? [];
754
927
  const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
755
928
  if (held === undefined || left.length === submitted.length)
@@ -760,8 +933,8 @@ export function createConversationStore(deps) {
760
933
  function moved(agent, operation, to) {
761
934
  const from = newChat(agent);
762
935
  return deps.durably(async (local) => {
763
- const held = drafts.get(from);
764
- const earlier = (held?.moves ?? []).filter((one) => !deps.expired(one));
936
+ const held = await draftAt(local, from);
937
+ const earlier = held?.moves ?? [];
765
938
  if (earlier.includes(operation))
766
939
  return;
767
940
  const blank = {
@@ -770,18 +943,20 @@ export function createConversationStore(deps) {
770
943
  revision: chatDraftRevision(),
771
944
  };
772
945
  const { moves: _answered, ...unsent } = held ?? blank;
773
- const taken = drafts.has(to) || empty(unsent) ? [] : [{ ...unsent, conversation: to }];
946
+ const taken = (await draftAt(local, to)) !== undefined || empty(unsent)
947
+ ? []
948
+ : [{ ...unsent, conversation: to }];
774
949
  const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
775
950
  await local.keepChatDrafts([left, ...taken]);
776
- drafts.set(from, left);
951
+ shownDraft(from, left);
777
952
  for (const one of taken)
778
- drafts.set(to, one);
953
+ shownDraft(to, one);
779
954
  deps.publish(conversationChangeSet([from, to]));
780
955
  });
781
956
  }
782
957
  function withDraft(conversation, next) {
783
958
  return deps.durably(async (local) => {
784
- const following = await next(drafts.get(conversation), local);
959
+ const following = await next(await draftAt(local, conversation), local);
785
960
  if (following === undefined)
786
961
  return;
787
962
  await kept(local, conversation, following);
@@ -790,10 +965,8 @@ export function createConversationStore(deps) {
790
965
  }
791
966
  async function submitted(conversation, operation, draft) {
792
967
  let submission;
793
- let earlier = false;
794
968
  await withDraft(conversation, (current) => {
795
969
  submission = current?.submitted?.find((one) => one.operation === operation);
796
- earlier = submission !== undefined;
797
970
  if (submission !== undefined)
798
971
  return undefined;
799
972
  const said = draft ?? current;
@@ -806,15 +979,7 @@ export function createConversationStore(deps) {
806
979
  submitted: [...(current?.submitted ?? []), submission],
807
980
  };
808
981
  });
809
- return { submission: submission, earlier };
810
- }
811
- function forgotten(conversation, submission) {
812
- return withDraft(conversation, (current) => {
813
- const held = current?.submitted ?? [];
814
- if (current === undefined || !held.includes(submission))
815
- return undefined;
816
- return withSubmitted(current, held.filter((one) => one !== submission));
817
- });
982
+ return submission;
818
983
  }
819
984
  async function dispatched(conversation, submission) {
820
985
  const dispatch = { answered: false };
@@ -873,7 +1038,8 @@ export function createConversationStore(deps) {
873
1038
  return deps.durably(async (local) => {
874
1039
  const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before, phrase);
875
1040
  const total = asked.total ? await local.messageCount(conversation, phrase) : null;
876
- return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total)));
1041
+ const marked = await local.jobMarkers(conversation);
1042
+ return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total), (await local.chatDraft(conversation)) ?? null, await endedIn(conversation), marked));
877
1043
  });
878
1044
  },
879
1045
  async create(input) {
@@ -881,8 +1047,10 @@ export function createConversationStore(deps) {
881
1047
  const conversation = answered(result, 'conversation_id');
882
1048
  if (conversation === null)
883
1049
  return result;
884
- if (!listing.has(conversation))
1050
+ const listed = (await heldList()) ?? [];
1051
+ if (!listed.some((entry) => entry.conversation === conversation)) {
885
1052
  creations.add(conversation);
1053
+ }
886
1054
  const creation = creationOf(input);
887
1055
  if (creation !== null)
888
1056
  await moved(creation.agent, creation.operation, conversation);
@@ -899,11 +1067,7 @@ export function createConversationStore(deps) {
899
1067
  });
900
1068
  if (held === null)
901
1069
  return DURABLE_WRITE;
902
- const { submission, earlier } = held;
903
- if (!earlier || !deps.expired(operation))
904
- return dispatched(conversation, submission);
905
- await forgotten(conversation, submission);
906
- return EXPIRED;
1070
+ return dispatched(conversation, held);
907
1071
  }
908
1072
  if (draft !== undefined) {
909
1073
  await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
@@ -954,43 +1118,40 @@ export function createConversationStore(deps) {
954
1118
  return {
955
1119
  surface,
956
1120
  chatDraft,
957
- hydrate(held, kept, list, cursor) {
958
- if (list !== null && !listKnown) {
959
- listing.clear();
960
- for (const entry of list)
961
- listing.set(entry.conversation, { ...entry });
962
- listHeld = true;
963
- listCursor = cursor;
964
- deps.stream()?.hold(cursor);
965
- }
966
- stored.clear();
967
- reads.clear();
968
- partials.clear();
969
- for (const entry of held) {
970
- stored.add(entry.conversation);
971
- if (entry.read !== undefined)
972
- reads.set(entry.conversation, entry.read);
973
- if (entry.partial)
974
- partials.add(entry.conversation);
975
- }
976
- drafts.clear();
977
- for (const record of kept)
978
- drafts.set(record.conversation, record);
979
- if (listHeld)
980
- learned();
1121
+ async opened(local) {
1122
+ const { conversationList, conversationCursor } = await local.facts();
1123
+ if (conversationList === null || listKnown)
1124
+ return;
1125
+ deps.stream()?.hold(conversationCursor);
1126
+ learned();
981
1127
  },
982
1128
  async reconcile() {
983
1129
  if (!listKnown)
984
1130
  return;
985
- const named = new Set(listing.keys());
1131
+ const local = await deps.local();
1132
+ const [held, kept, { conversationList }] = await Promise.all([
1133
+ local.conversations(),
1134
+ local.chatDrafts(),
1135
+ local.facts(),
1136
+ ]);
1137
+ const list = conversationList ?? [];
1138
+ const entries = new Map(list.map((entry) => [entry.conversation, entry]));
1139
+ const named = new Set(entries.keys());
1140
+ const reads = new Map(held.map((one) => [one.conversation, one]));
986
1141
  const leaving = [
987
- ...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
1142
+ ...new Set([
1143
+ ...replicated,
1144
+ ...reads.keys(),
1145
+ ...kept.map(({ conversation }) => conversation),
1146
+ ]),
988
1147
  ].filter((conversation) => !named.has(conversation) &&
989
1148
  !creations.has(conversation) &&
990
- (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
1149
+ (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation, list)));
991
1150
  for (const conversation of named) {
992
- if (!replicated.has(conversation) && missed(conversation))
1151
+ if (!replicated.has(conversation) &&
1152
+ missed(conversation, reads.get(conversation), entries.get(conversation))) {
993
1153
  behind.add(conversation);
1154
+ }
994
1155
  }
995
1156
  replicated = named;
996
1157
  handReplicated();
@@ -1021,16 +1182,16 @@ export function createConversationStore(deps) {
1021
1182
  progress.complete = false;
1022
1183
  },
1023
1184
  async resend(progress) {
1024
- for (const [conversation, held] of [...drafts]) {
1025
- for (const submission of held.submitted ?? []) {
1026
- if (submission.turn !== null || sending.has(submission.operation))
1027
- continue;
1185
+ const local = await deps.local();
1186
+ const tried = new Set();
1187
+ for (const { conversation } of await local.chatDrafts()) {
1188
+ for (;;) {
1189
+ const submission = (await local.chatDraft(conversation))?.submitted?.find((one) => one.turn === null && !sending.has(one.operation) && !tried.has(one.operation));
1190
+ if (submission === undefined)
1191
+ break;
1028
1192
  if (deps.closed())
1029
1193
  return;
1030
- if (deps.expired(submission.operation)) {
1031
- await forgotten(conversation, submission);
1032
- continue;
1033
- }
1194
+ tried.add(submission.operation);
1034
1195
  const result = await dispatched(conversation, submission);
1035
1196
  if (result.kind === 'revoked-authorization')
1036
1197
  return;
@@ -1068,6 +1229,9 @@ export function createConversationStore(deps) {
1068
1229
  asked,
1069
1230
  watchers: new Set(),
1070
1231
  window: null,
1232
+ draft: null,
1233
+ ended: null,
1234
+ marked: [],
1071
1235
  more: false,
1072
1236
  total: null,
1073
1237
  };
@@ -1079,7 +1243,10 @@ export function createConversationStore(deps) {
1079
1243
  if (view.window !== null)
1080
1244
  return;
1081
1245
  const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
1246
+ view.draft = (await local.chatDraft(conversation)) ?? null;
1247
+ view.ended = await endedIn(conversation);
1082
1248
  view.window = messages;
1249
+ view.marked = await local.jobMarkers(conversation);
1083
1250
  view.more = more;
1084
1251
  view.total = asked.total ? await local.messageCount(conversation) : null;
1085
1252
  }), reading.signal)
@@ -1088,7 +1255,7 @@ export function createConversationStore(deps) {
1088
1255
  return;
1089
1256
  watcher.ready = true;
1090
1257
  const window = view.window ?? [];
1091
- watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
1258
+ watcher.notify(viewOf(conversation, window, sharedFrame(view, window), view.draft, view.ended, view.marked));
1092
1259
  })
1093
1260
  .catch(() => { });
1094
1261
  return () => {
@@ -1123,58 +1290,43 @@ export function createConversationStore(deps) {
1123
1290
  },
1124
1291
  receive(event) {
1125
1292
  if (deps.closed())
1126
- return false;
1127
- if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
1128
- if (event.kind === 'list') {
1129
- if (event.whole)
1130
- relisting ??= new Map();
1131
- for (const entry of event.entries) {
1132
- if (relisting === null)
1133
- listed(entry);
1134
- else
1135
- relisting.set(entry.conversation, { ...entry });
1136
- }
1137
- if (event.more)
1138
- return false;
1139
- if (relisting !== null) {
1140
- listing.clear();
1141
- for (const entry of relisting.values())
1142
- listed(entry);
1143
- relisting = null;
1144
- }
1145
- listCursor = event.cursor;
1146
- deps.stream()?.hold(event.cursor);
1147
- listKnown = true;
1148
- for (const conversation of replicated) {
1149
- if (!listing.has(conversation))
1150
- continue;
1151
- if (!missed(conversation))
1152
- behind.delete(conversation);
1153
- else if (!behind.has(conversation))
1154
- gap(conversation);
1155
- }
1156
- openingEnded();
1157
- }
1158
- else if (event.kind === 'listed') {
1159
- listed(event.entry);
1160
- relisting?.set(event.entry.conversation, { ...event.entry });
1161
- }
1162
- else {
1163
- listing.delete(event.conversation);
1164
- relisting?.delete(event.conversation);
1293
+ return;
1294
+ if (event.kind === 'list') {
1295
+ if (event.whole)
1296
+ relisting ??= new Map();
1297
+ if (relisting !== null) {
1298
+ for (const entry of event.entries)
1299
+ relisting.set(entry.conversation, { ...entry });
1165
1300
  }
1166
- keepList();
1167
- emitList();
1168
- return ((event.kind !== 'list' &&
1169
- listKnown &&
1170
- (listing.size !== replicated.size ||
1171
- [...listing.keys()].some((conversation) => !replicated.has(conversation)))) ||
1172
- (listKnown && [...drafts.keys()].some(retiredChat)));
1301
+ if (event.more && relisting !== null)
1302
+ return;
1303
+ const whole = relisting;
1304
+ relisting = null;
1305
+ listChanged({
1306
+ replacing: whole !== null,
1307
+ set: whole === null ? event.entries : [...whole.values()],
1308
+ gone: null,
1309
+ ...(event.more ? { paged: true } : { cursor: event.cursor }),
1310
+ });
1311
+ return;
1312
+ }
1313
+ if (event.kind === 'listed') {
1314
+ relisting?.set(event.entry.conversation, { ...event.entry });
1315
+ listChanged({ replacing: false, set: [event.entry], gone: null });
1316
+ return;
1317
+ }
1318
+ if (event.kind === 'unlisted') {
1319
+ relisting?.delete(event.conversation);
1320
+ listChanged({ replacing: false, set: [], gone: event.conversation });
1321
+ return;
1173
1322
  }
1174
1323
  heard(event);
1175
- return false;
1176
1324
  },
1177
1325
  close() {
1326
+ closing.abort();
1327
+ if (rowTimer !== null)
1328
+ clearTimeout(rowTimer);
1329
+ rowTimer = null;
1178
1330
  views.clear();
1179
1331
  lives.clear();
1180
1332
  unsent.clear();