@agentshouse/mdruntime 0.1.0-c99e5271060cd → 0.1.0-ca10e63188fc5

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 (176) hide show
  1. package/README.md +21 -8
  2. package/dist/adapter.d.ts +134 -30
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/attached-model.d.ts +5 -1
  5. package/dist/attached-model.d.ts.map +1 -1
  6. package/dist/attached-model.js +21 -4
  7. package/dist/attempt.d.ts +16 -0
  8. package/dist/attempt.d.ts.map +1 -0
  9. package/dist/attempt.js +70 -0
  10. package/dist/binary.d.ts +18 -0
  11. package/dist/binary.d.ts.map +1 -0
  12. package/dist/binary.js +8 -0
  13. package/dist/bootstrap.d.ts +32 -9
  14. package/dist/bootstrap.d.ts.map +1 -1
  15. package/dist/bootstrap.js +192 -67
  16. package/dist/chain.d.ts +11 -0
  17. package/dist/chain.d.ts.map +1 -0
  18. package/dist/chain.js +64 -0
  19. package/dist/change-set.d.ts +9 -14
  20. package/dist/change-set.d.ts.map +1 -1
  21. package/dist/change-set.js +11 -62
  22. package/dist/change.d.ts +41 -0
  23. package/dist/change.d.ts.map +1 -1
  24. package/dist/change.js +34 -0
  25. package/dist/client.d.ts +31 -12
  26. package/dist/client.d.ts.map +1 -1
  27. package/dist/client.js +104 -7
  28. package/dist/collection.d.ts +12 -6
  29. package/dist/collection.d.ts.map +1 -1
  30. package/dist/collection.js +51 -76
  31. package/dist/compile.d.ts +16 -0
  32. package/dist/compile.d.ts.map +1 -0
  33. package/dist/compile.js +765 -0
  34. package/dist/connection.d.ts +52 -16
  35. package/dist/connection.d.ts.map +1 -1
  36. package/dist/connection.js +3656 -766
  37. package/dist/conversation.d.ts +37 -11
  38. package/dist/conversation.d.ts.map +1 -1
  39. package/dist/conversation.js +576 -179
  40. package/dist/digest.d.ts +7 -0
  41. package/dist/digest.d.ts.map +1 -0
  42. package/dist/digest.js +113 -0
  43. package/dist/durability.d.ts +5 -1
  44. package/dist/durability.d.ts.map +1 -1
  45. package/dist/durability.js +5 -0
  46. package/dist/entry.d.ts +19 -0
  47. package/dist/entry.d.ts.map +1 -0
  48. package/dist/entry.js +151 -0
  49. package/dist/expectation.d.ts +21 -0
  50. package/dist/expectation.d.ts.map +1 -0
  51. package/dist/expectation.js +70 -0
  52. package/dist/fence.d.ts +6 -0
  53. package/dist/fence.d.ts.map +1 -0
  54. package/dist/fence.js +10 -0
  55. package/dist/file.d.ts +2 -19
  56. package/dist/file.d.ts.map +1 -1
  57. package/dist/file.js +7 -122
  58. package/dist/fulltext.d.ts +27 -0
  59. package/dist/fulltext.d.ts.map +1 -0
  60. package/dist/fulltext.js +368 -0
  61. package/dist/host.d.ts +15 -3
  62. package/dist/host.d.ts.map +1 -1
  63. package/dist/host.js +131 -15
  64. package/dist/identity.d.ts +3 -0
  65. package/dist/identity.d.ts.map +1 -1
  66. package/dist/identity.js +9 -0
  67. package/dist/index.d.ts +18 -9
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +8 -0
  70. package/dist/live.d.ts +22 -0
  71. package/dist/live.d.ts.map +1 -0
  72. package/dist/live.js +124 -0
  73. package/dist/media.d.ts +8 -0
  74. package/dist/media.d.ts.map +1 -0
  75. package/dist/media.js +205 -0
  76. package/dist/memory.d.ts +12 -5
  77. package/dist/memory.d.ts.map +1 -1
  78. package/dist/memory.js +112 -24
  79. package/dist/messages.d.ts +28 -0
  80. package/dist/messages.d.ts.map +1 -0
  81. package/dist/messages.js +90 -0
  82. package/dist/model.d.ts +10 -2
  83. package/dist/model.d.ts.map +1 -1
  84. package/dist/model.js +17 -2
  85. package/dist/name.d.ts +4 -0
  86. package/dist/name.d.ts.map +1 -0
  87. package/dist/name.js +122 -0
  88. package/dist/observation.d.ts +20 -2
  89. package/dist/observation.d.ts.map +1 -1
  90. package/dist/observation.js +113 -12
  91. package/dist/opened.d.ts +22 -0
  92. package/dist/opened.d.ts.map +1 -0
  93. package/dist/opened.js +107 -0
  94. package/dist/outcome.d.ts +52 -1
  95. package/dist/outcome.d.ts.map +1 -1
  96. package/dist/outcome.js +46 -0
  97. package/dist/overlay.d.ts +15 -5
  98. package/dist/overlay.d.ts.map +1 -1
  99. package/dist/overlay.js +129 -177
  100. package/dist/query.d.ts +68 -4
  101. package/dist/query.d.ts.map +1 -1
  102. package/dist/query.js +48 -7
  103. package/dist/reach.d.ts +17 -4
  104. package/dist/reach.d.ts.map +1 -1
  105. package/dist/reach.js +226 -50
  106. package/dist/react.d.ts +10 -7
  107. package/dist/react.d.ts.map +1 -1
  108. package/dist/react.js +7 -17
  109. package/dist/readiness.d.ts +64 -0
  110. package/dist/readiness.d.ts.map +1 -0
  111. package/dist/readiness.js +33 -0
  112. package/dist/reads.d.ts +11 -0
  113. package/dist/reads.d.ts.map +1 -0
  114. package/dist/reads.js +100 -0
  115. package/dist/recovery.d.ts +25 -0
  116. package/dist/recovery.d.ts.map +1 -0
  117. package/dist/recovery.js +198 -0
  118. package/dist/refresh.d.ts +16 -15
  119. package/dist/refresh.d.ts.map +1 -1
  120. package/dist/refresh.js +113 -54
  121. package/dist/replica.d.ts +59 -33
  122. package/dist/replica.d.ts.map +1 -1
  123. package/dist/replica.js +309 -157
  124. package/dist/room-draft.d.ts +35 -3
  125. package/dist/room-draft.d.ts.map +1 -1
  126. package/dist/room-draft.js +174 -16
  127. package/dist/rows.d.ts +37 -35
  128. package/dist/rows.d.ts.map +1 -1
  129. package/dist/rows.js +173 -102
  130. package/dist/save.d.ts +68 -3
  131. package/dist/save.d.ts.map +1 -1
  132. package/dist/save.js +795 -143
  133. package/dist/search.d.ts +39 -28
  134. package/dist/search.d.ts.map +1 -1
  135. package/dist/search.js +37 -105
  136. package/dist/seed.d.ts +16 -0
  137. package/dist/seed.d.ts.map +1 -0
  138. package/dist/seed.js +75 -0
  139. package/dist/shared.d.ts +16 -0
  140. package/dist/shared.d.ts.map +1 -0
  141. package/dist/shared.js +431 -0
  142. package/dist/status.d.ts +4 -1
  143. package/dist/status.d.ts.map +1 -1
  144. package/dist/status.js +0 -8
  145. package/dist/store.d.ts +126 -80
  146. package/dist/store.d.ts.map +1 -1
  147. package/dist/store.js +371 -457
  148. package/dist/stored.d.ts +152 -0
  149. package/dist/stored.d.ts.map +1 -0
  150. package/dist/stored.js +571 -0
  151. package/dist/tables.d.ts +354 -0
  152. package/dist/tables.d.ts.map +1 -0
  153. package/dist/tables.js +624 -0
  154. package/dist/turn.d.ts +9 -0
  155. package/dist/turn.d.ts.map +1 -0
  156. package/dist/turn.js +75 -0
  157. package/dist/validation.d.ts +30 -0
  158. package/dist/validation.d.ts.map +1 -0
  159. package/dist/validation.js +162 -0
  160. package/dist/windows.d.ts +22 -0
  161. package/dist/windows.d.ts.map +1 -0
  162. package/dist/windows.js +89 -0
  163. package/dist/wire.d.ts +169 -30
  164. package/dist/wire.d.ts.map +1 -1
  165. package/dist/wire.js +57 -29
  166. package/dist/words.d.ts +19 -0
  167. package/dist/words.d.ts.map +1 -0
  168. package/dist/words.js +150 -0
  169. package/package.json +26 -15
  170. package/AGENTS.md +0 -269
  171. package/dist/attachment.d.ts +0 -3
  172. package/dist/attachment.d.ts.map +0 -1
  173. package/dist/attachment.js +0 -8
  174. package/dist/read-side.d.ts +0 -18
  175. package/dist/read-side.d.ts.map +0 -1
  176. package/dist/read-side.js +0 -433
@@ -1,13 +1,28 @@
1
1
  import { conversationChangeSet } from './change-set.js';
2
2
  import { runtimeError } from './failure.js';
3
- import { declaredFile } from './file.js';
3
+ import { refusedForSpace } from './durability.js';
4
+ import { changeBatchIdentity, chatDraftRevision } from './identity.js';
5
+ import { OFFLINE } from './outcome.js';
6
+ import { capped, cursorOf, decodedCursor, FROM_START, messageOrder, precedes } from './windows.js';
4
7
  const QUIET = {
5
8
  turn: null,
6
9
  questions: [],
7
10
  process: null,
8
11
  commands: [],
9
12
  options: [],
13
+ starting: null,
14
+ link: null,
15
+ deliveryUnknown: [],
10
16
  };
17
+ const FACTS = [
18
+ 'questions',
19
+ 'process',
20
+ 'commands',
21
+ 'options',
22
+ 'starting',
23
+ 'link',
24
+ 'deliveryUnknown',
25
+ ];
11
26
  export function unheldView(conversation) {
12
27
  return {
13
28
  conversation,
@@ -17,9 +32,26 @@ export function unheldView(conversation) {
17
32
  process: null,
18
33
  commands: [],
19
34
  options: [],
35
+ starting: null,
36
+ link: null,
37
+ deliveryUnknown: [],
20
38
  chatDraft: null,
21
39
  };
22
40
  }
41
+ export const SESSION_STARTED = 'Session started';
42
+ const NEW_CHAT = 'new:';
43
+ export const HISTORY_CONVERSATIONS = 64;
44
+ export function newChat(agent) {
45
+ return `${NEW_CHAT}${agent}`;
46
+ }
47
+ function creationOf(input) {
48
+ if (typeof input !== 'object' || input === null)
49
+ return null;
50
+ const { agent_id: agent, operation_id: operation, replaces, } = input;
51
+ return typeof agent === 'string' && typeof operation === 'string' && replaces === undefined
52
+ ? { agent, operation }
53
+ : null;
54
+ }
23
55
  function startedTurn(turn, operation) {
24
56
  if (operation.op === 'begin')
25
57
  return { turn, started: operation.at, parts: [], context: null };
@@ -69,18 +101,28 @@ function appliedTurn(held, operation) {
69
101
  parts: held.parts.map((kept, at) => (at === operation.index ? changed : kept)),
70
102
  };
71
103
  }
72
- function ordered(left, right) {
73
- if (left.position !== right.position)
74
- return left.position < right.position ? -1 : 1;
75
- if (left.identity !== right.identity)
76
- return left.identity < right.identity ? -1 : 1;
77
- return 0;
78
- }
79
- function merged(held, arriving) {
104
+ function windowOf(held, arriving, limit) {
80
105
  const byIdentity = new Map(held.map((message) => [message.identity, message]));
81
106
  for (const message of arriving)
82
107
  byIdentity.set(message.identity, message);
83
- return [...byIdentity.values()].sort(ordered);
108
+ const next = [...byIdentity.values()].sort(messageOrder);
109
+ const start = Math.max(0, next.length - limit);
110
+ return { messages: next.slice(start), dropped: start > 0 };
111
+ }
112
+ function frameOf(asked, messages, more, total) {
113
+ const oldest = messages[0];
114
+ const next = !more
115
+ ? undefined
116
+ : oldest === undefined
117
+ ? (asked.after ?? FROM_START)
118
+ : cursorOf([oldest.position, oldest.identity]);
119
+ return {
120
+ ...(next === undefined ? {} : { next }),
121
+ ...(total === null ? {} : { total }),
122
+ };
123
+ }
124
+ function newestOf(arriving, from) {
125
+ return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
84
126
  }
85
127
  function acceptedMessage(value) {
86
128
  if (typeof value !== 'object' || value === null)
@@ -139,86 +181,156 @@ function historyPages(result) {
139
181
  }
140
182
  return pages;
141
183
  }
142
- function turnOf(result) {
184
+ function answered(result, field) {
143
185
  if (result.kind !== 'action')
144
186
  return null;
145
187
  const answer = result.result;
146
188
  if (typeof answer !== 'object' || answer === null)
147
189
  return null;
148
- const record = answer;
149
- return typeof record.turn === 'string' ? record.turn : null;
190
+ const value = answer[field];
191
+ return typeof value === 'string' ? value : null;
150
192
  }
151
- function limitOf(options) {
152
- const { limit } = options;
153
- if (limit === undefined)
154
- return undefined;
155
- if (!Number.isInteger(limit) || limit < 0) {
193
+ function beforeOf(after) {
194
+ if (after === undefined)
195
+ return null;
196
+ const decoded = typeof after === 'string' ? decodedCursor(after) : null;
197
+ if (decoded?.length === 0)
198
+ return null;
199
+ const [position, identity] = decoded ?? [];
200
+ if (decoded?.length !== 2 || typeof position !== 'string' || typeof identity !== 'string') {
201
+ throw runtimeError('invalid-after', `conversation after must be the next an earlier view returned, got ${String(after)}`);
202
+ }
203
+ return [position, identity];
204
+ }
205
+ function askedOf(options) {
206
+ const { limit, after, total } = options;
207
+ if (limit !== undefined && (!Number.isInteger(limit) || limit < 0)) {
156
208
  throw runtimeError('invalid-limit', `conversation limit must be a non-negative integer, got ${String(limit)}`);
157
209
  }
158
- return limit;
210
+ return { limit: capped(limit), after, before: beforeOf(after), total: total === true };
211
+ }
212
+ const EXPIRED = { kind: 'rejected', code: 'expired', fields: {}, retryable: false };
213
+ const DURABLE_WRITE = {
214
+ kind: 'rejected',
215
+ code: 'durable-write',
216
+ fields: {},
217
+ retryable: true,
218
+ };
219
+ function unwritten(draft) {
220
+ return draft.text === '';
221
+ }
222
+ function pending(draft) {
223
+ return {
224
+ ...(draft.submitted === undefined ? {} : { submitted: draft.submitted }),
225
+ ...(draft.moves === undefined ? {} : { moves: draft.moves }),
226
+ };
227
+ }
228
+ function empty(draft) {
229
+ return unwritten(draft) && draft.submitted === undefined && draft.moves === undefined;
230
+ }
231
+ function withSubmitted(draft, submitted) {
232
+ const { submitted: _held, ...unsent } = draft;
233
+ return submitted.length === 0 ? unsent : { ...unsent, submitted };
234
+ }
235
+ function prepended(draft, said) {
236
+ return { text: [said.text, draft.text].filter((one) => one !== '').join('\n') };
237
+ }
238
+ function returned(draft, submission, others) {
239
+ return withSubmitted({
240
+ conversation: draft.conversation,
241
+ ...prepended(draft, submission),
242
+ revision: chatDraftRevision(),
243
+ }, others);
159
244
  }
160
245
  export function createConversationStore(deps) {
161
- const messages = new Map();
246
+ const stored = new Set();
162
247
  const reads = new Map();
248
+ const partials = new Set();
163
249
  const drafts = new Map();
164
250
  const sending = new Map();
165
251
  const behind = new Set();
252
+ const catching = new Map();
166
253
  const gaps = new Map();
167
254
  const lives = new Map();
255
+ const unsent = new Map();
168
256
  const views = new Map();
169
257
  const listing = new Map();
258
+ const creations = new Set();
170
259
  const listWatchers = new Set();
171
260
  let listKnown = false;
261
+ const agentWatchers = new Set();
172
262
  let agentNames = new Map();
263
+ let retiredAgents = new Set();
173
264
  let agentsRead = null;
174
265
  let replicated = new Set();
175
266
  let handedReplicated;
176
267
  function liveOf(conversation) {
177
268
  return lives.get(conversation) ?? QUIET;
178
269
  }
179
- function shownMessages(conversation, limit) {
180
- const held = messages.get(conversation) ?? [];
181
- const shown = limit === undefined ? held : held.slice(Math.max(0, held.length - limit));
182
- return shown.map(({ identity, turn, text, message }) => ({
183
- identity,
184
- turn,
185
- text,
186
- message: structuredClone(message),
187
- }));
188
- }
189
- function viewOf(conversation, limit, shown = shownMessages(conversation, limit)) {
270
+ function viewOf(conversation, held, frame) {
190
271
  const draft = drafts.get(conversation);
191
272
  const facts = liveOf(conversation);
192
273
  return {
193
274
  conversation,
194
- messages: shown,
275
+ messages: held.map(({ identity, turn, text, message }) => ({
276
+ identity,
277
+ turn,
278
+ text,
279
+ message,
280
+ })),
281
+ ...frame,
195
282
  turn: structuredClone(facts.turn),
196
283
  questions: structuredClone(facts.questions),
197
284
  process: facts.process === null ? null : { ...facts.process },
198
285
  commands: structuredClone(facts.commands),
199
286
  options: structuredClone(facts.options),
200
- chatDraft: draft === undefined || draft.turn !== null || sending.get(conversation)?.draft === draft
287
+ starting: facts.starting,
288
+ link: facts.link === null ? null : { ...facts.link },
289
+ deliveryUnknown: [...facts.deliveryUnknown],
290
+ chatDraft: draft === undefined || unwritten(draft)
201
291
  ? null
202
- : { text: draft.text, files: [...draft.files] },
292
+ : { text: draft.text, revision: draft.revision },
203
293
  };
204
294
  }
205
- function emit(conversation, messagesChanged = true) {
295
+ function emit(conversation) {
206
296
  if (deps.closed())
207
297
  return;
208
298
  for (const view of views.values()) {
209
- if (view.conversation !== conversation)
299
+ if (view.conversation !== conversation || view.window === null)
210
300
  continue;
211
- if (messagesChanged)
212
- view.messages = null;
213
301
  const ready = [...view.watchers].filter((watcher) => watcher.ready);
214
302
  if (ready.length === 0)
215
303
  continue;
216
- view.messages ??= shownMessages(conversation, view.limit);
217
- const value = viewOf(conversation, view.limit, view.messages);
304
+ const value = viewOf(conversation, view.window, sharedFrame(view, view.window));
218
305
  for (const watcher of ready)
219
306
  watcher.notify(value);
220
307
  }
221
308
  }
309
+ function sharedFrame(view, window) {
310
+ return frameOf(view.asked, window, view.more, view.total);
311
+ }
312
+ async function slide(local, conversation, arriving) {
313
+ let count;
314
+ for (const view of views.values()) {
315
+ if (view.conversation !== conversation || view.window === null)
316
+ continue;
317
+ if (arriving === null) {
318
+ view.window = [];
319
+ view.more = false;
320
+ if (view.total !== null)
321
+ view.total = 0;
322
+ continue;
323
+ }
324
+ const joining = arriving.filter((message) => precedes(message, view.asked.before));
325
+ const next = windowOf(view.window, joining, view.asked.limit);
326
+ view.window = next.messages;
327
+ view.more ||= next.dropped;
328
+ if (view.total !== null && arriving.length > 0) {
329
+ count ??= await local.messageCount(conversation);
330
+ view.total = count;
331
+ }
332
+ }
333
+ }
222
334
  function stateOf(conversation) {
223
335
  const facts = liveOf(conversation);
224
336
  if (facts.questions.length > 0)
@@ -233,6 +345,7 @@ export function createConversationStore(deps) {
233
345
  created: entry.created,
234
346
  finished: entry.finished,
235
347
  outcome: entry.outcome,
348
+ waiting: entry.waiting,
236
349
  state: stateOf(entry.conversation),
237
350
  started: lives.get(entry.conversation)?.turn?.started ?? null,
238
351
  }));
@@ -244,6 +357,21 @@ export function createConversationStore(deps) {
244
357
  for (const watcher of listWatchers)
245
358
  watcher(value);
246
359
  }
360
+ function agents() {
361
+ return [...agentNames]
362
+ .filter(([identity]) => !retiredAgents.has(identity))
363
+ .map(([identity, name]) => ({ identity, name }))
364
+ .sort((left, right) => left.name === right.name
365
+ ? left.identity.localeCompare(right.identity)
366
+ : left.name.localeCompare(right.name));
367
+ }
368
+ function emitAgents() {
369
+ if (deps.closed() || agentWatchers.size === 0)
370
+ return;
371
+ const value = agents();
372
+ for (const watcher of agentWatchers)
373
+ watcher(value);
374
+ }
247
375
  function setLive(conversation, next) {
248
376
  const before = stateOf(conversation);
249
377
  const began = lives.get(conversation)?.turn?.started;
@@ -260,20 +388,45 @@ export function createConversationStore(deps) {
260
388
  const declaration = deps.agents();
261
389
  if (declaration === undefined)
262
390
  return;
263
- const { documents } = await deps.documents(declaration.locator);
264
391
  const names = new Map();
265
- for (const document of documents) {
266
- if (document.type !== declaration.type)
267
- continue;
268
- const identity = document.fields?.[declaration.identity];
269
- const name = document.fields?.[declaration.name];
270
- if (typeof identity === 'string' && typeof name === 'string')
392
+ const retired = new Set();
393
+ let covered = null;
394
+ for (let after;;) {
395
+ const read = await deps.documents(declaration, after);
396
+ if ('pending' in read)
397
+ return;
398
+ const coverage = JSON.stringify(read.coverage);
399
+ if (covered !== null && coverage !== covered)
400
+ return readAgents();
401
+ covered = coverage;
402
+ for (const document of read.documents) {
403
+ const identity = document.fields?.[declaration.identity];
404
+ const name = document.fields?.[declaration.name];
405
+ if (typeof identity !== 'string' || typeof name !== 'string')
406
+ continue;
271
407
  names.set(identity, name);
408
+ const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
409
+ if (ended !== undefined && ended !== null)
410
+ retired.add(identity);
411
+ }
412
+ if (read.next === undefined)
413
+ break;
414
+ after = read.next;
272
415
  }
273
- if (JSON.stringify([...names]) === JSON.stringify([...agentNames]))
416
+ const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
417
+ if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents]))
274
418
  return;
275
419
  agentNames = names;
276
- emitList();
420
+ retiredAgents = retired;
421
+ if (named)
422
+ emitList();
423
+ emitAgents();
424
+ const ended = [...drafts.keys()].filter(retiredChat);
425
+ if (ended.length > 0)
426
+ await remove(ended);
427
+ }
428
+ function retiredChat(conversation) {
429
+ return (conversation.startsWith(NEW_CHAT) && retiredAgents.has(conversation.slice(NEW_CHAT.length)));
277
430
  }
278
431
  function agentsReady() {
279
432
  agentsRead ??= readAgents().catch(() => {
@@ -282,8 +435,33 @@ export function createConversationStore(deps) {
282
435
  return agentsRead;
283
436
  }
284
437
  function live(conversation, change) {
438
+ const owed = unsent.get(conversation);
439
+ for (const fact of FACTS.filter((one) => one in change))
440
+ owed?.delete(fact);
285
441
  setLive(conversation, { ...liveOf(conversation), ...change });
286
- emit(conversation, false);
442
+ emit(conversation);
443
+ }
444
+ function openingEnded() {
445
+ const owed = [...unsent];
446
+ unsent.clear();
447
+ for (const [conversation, facts] of owed) {
448
+ const held = lives.get(conversation);
449
+ if (held === undefined || facts.size === 0)
450
+ continue;
451
+ setLive(conversation, {
452
+ turn: held.turn,
453
+ questions: facts.has('questions') ? QUIET.questions : held.questions,
454
+ process: facts.has('process') ? QUIET.process : held.process,
455
+ commands: facts.has('commands') ? QUIET.commands : held.commands,
456
+ options: facts.has('options') ? QUIET.options : held.options,
457
+ starting: facts.has('starting') ? QUIET.starting : held.starting,
458
+ link: facts.has('link') ? QUIET.link : held.link,
459
+ deliveryUnknown: facts.has('deliveryUnknown')
460
+ ? QUIET.deliveryUnknown
461
+ : held.deliveryUnknown,
462
+ });
463
+ emit(conversation);
464
+ }
287
465
  }
288
466
  function relayed(conversation, turn, operation) {
289
467
  const held = liveOf(conversation).turn;
@@ -312,23 +490,22 @@ export function createConversationStore(deps) {
312
490
  async function remove(leaving) {
313
491
  await deps.durably(async (local) => {
314
492
  await local.dropConversations(leaving);
315
- const removed = [];
316
493
  const changed = [];
317
494
  for (const conversation of leaving) {
318
- for (const message of messages.get(conversation) ?? []) {
319
- removed.push({ conversation, identity: message.identity });
320
- }
321
- if (messages.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
495
+ if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
322
496
  changed.push(conversation);
323
497
  }
324
- messages.delete(conversation);
498
+ stored.delete(conversation);
325
499
  reads.delete(conversation);
500
+ partials.delete(conversation);
326
501
  drafts.delete(conversation);
327
502
  behind.delete(conversation);
503
+ unsent.delete(conversation);
328
504
  setLive(conversation, null);
505
+ await slide(local, conversation, null);
329
506
  }
330
507
  if (changed.length > 0)
331
- deps.publish(conversationChangeSet(removed, changed));
508
+ deps.publish(conversationChangeSet(changed));
332
509
  });
333
510
  }
334
511
  function gap(conversation) {
@@ -343,32 +520,28 @@ export function createConversationStore(deps) {
343
520
  const advances = read !== undefined &&
344
521
  !behind.has(conversation) &&
345
522
  (read === null || message.position > read);
346
- const sealed = drafts.get(conversation)?.turn === message.turn;
523
+ await sealed(local, conversation, new Set([message.turn]));
347
524
  await local.keepMessages({
348
525
  conversation,
349
526
  messages: [message],
350
- ...(advances ? { read: message.position } : {}),
351
- sealed,
527
+ ...(advances ? { read: { at: message.position, partial: false } } : {}),
352
528
  });
353
- messages.set(conversation, merged(messages.get(conversation) ?? [], [message]));
529
+ stored.add(conversation);
354
530
  if (advances)
355
531
  reads.set(conversation, message.position);
356
- if (sealed)
357
- drafts.delete(conversation);
358
532
  sealTurns(conversation, new Set([message.turn]));
359
- deps.publish(conversationChangeSet([{ conversation, identity: message.identity }], [conversation]));
533
+ await slide(local, conversation, [message]);
534
+ deps.publish(conversationChangeSet([conversation]));
360
535
  });
361
536
  }
362
- async function readHistories(conversations) {
537
+ async function readHistories(conversations, ended) {
363
538
  const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
364
- const starts = new Map(conversations.map((one) => [one, reads.get(one)]));
365
- const arriving = new Map();
366
- const complete = new Set();
367
- let asking = conversations.map((conversation) => ({
539
+ let waiting = conversations.map((conversation) => ({
368
540
  conversation,
369
- after: starts.get(conversation) ?? undefined,
541
+ after: reads.get(conversation) ?? undefined,
370
542
  }));
371
- while (asking.length > 0) {
543
+ while (waiting.length > 0) {
544
+ const asking = waiting.slice(0, HISTORY_CONVERSATIONS);
372
545
  const result = await deps.call('conversation.history', {
373
546
  conversations: asking.map(({ conversation, after }) => ({
374
547
  conversation,
@@ -378,46 +551,64 @@ export function createConversationStore(deps) {
378
551
  const pages = historyPages(result);
379
552
  if (pages === null)
380
553
  return result;
381
- const asked = new Set(asking.map(({ conversation }) => conversation));
554
+ const asked = new Map(asking.map(({ conversation, after }) => [conversation, after]));
382
555
  const following = [];
383
556
  for (const page of pages) {
384
557
  if (!asked.has(page.conversation))
385
558
  continue;
386
- arriving.set(page.conversation, [
387
- ...(arriving.get(page.conversation) ?? []),
388
- ...page.messages,
389
- ]);
390
- if (page.next === undefined)
391
- complete.add(page.conversation);
392
- else
559
+ await keepPage(page.conversation, page.messages, asked.get(page.conversation) ?? null, page.next, generations.get(page.conversation) ?? 0);
560
+ if (page.next !== undefined) {
393
561
  following.push({ conversation: page.conversation, after: page.next });
562
+ }
394
563
  }
395
- asking = following;
396
- }
397
- for (const [conversation, held] of arriving) {
398
- if (!complete.has(conversation))
399
- continue;
400
- await keepHistory(conversation, held, starts.get(conversation), generations.get(conversation) ?? 0);
564
+ for (const { conversation } of asking) {
565
+ if (!following.some((next) => next.conversation === conversation))
566
+ ended(conversation);
567
+ }
568
+ waiting = [...waiting.slice(HISTORY_CONVERSATIONS), ...following];
401
569
  }
402
570
  return null;
403
571
  }
404
- function keepHistory(conversation, arriving, from, generation) {
572
+ function caughtUp(conversations) {
573
+ const ends = new Map();
574
+ for (const conversation of conversations) {
575
+ const held = new Promise((end) => ends.set(conversation, end));
576
+ catching.set(conversation, held);
577
+ void held.then(() => {
578
+ if (catching.get(conversation) === held)
579
+ catching.delete(conversation);
580
+ });
581
+ }
582
+ const ended = (conversation) => ends.get(conversation)?.();
583
+ const read = readHistories(conversations, ended);
584
+ const endAll = () => {
585
+ for (const conversation of conversations)
586
+ ended(conversation);
587
+ };
588
+ read.then(endAll, endAll);
589
+ return read;
590
+ }
591
+ function keepPage(conversation, arriving, from, next, generation) {
405
592
  return deps.durably(async (local) => {
406
593
  if (!replicated.has(conversation))
407
594
  return;
408
- const newest = arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from ?? null);
409
- const turn = drafts.get(conversation)?.turn;
410
- const sealed = arriving.some((message) => message.turn === turn);
411
- await local.keepMessages({ conversation, messages: arriving, read: newest, sealed });
412
- messages.set(conversation, merged(messages.get(conversation) ?? [], arriving));
413
- reads.set(conversation, newest);
414
- if (sealed)
415
- drafts.delete(conversation);
595
+ const whole = next === undefined;
596
+ const cursor = next ?? newestOf(arriving, from);
597
+ const partial = !whole && (partials.has(conversation) || !reads.has(conversation));
598
+ const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
599
+ await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
600
+ stored.add(conversation);
601
+ reads.set(conversation, cursor);
602
+ if (partial)
603
+ partials.add(conversation);
604
+ else
605
+ partials.delete(conversation);
416
606
  sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
417
- if ((gaps.get(conversation) ?? 0) === generation)
607
+ if (whole && (gaps.get(conversation) ?? 0) === generation)
418
608
  behind.delete(conversation);
419
- if (arriving.length > 0 || sealed) {
420
- deps.publish(conversationChangeSet(arriving.map((message) => ({ conversation, identity: message.identity })), [conversation]));
609
+ await slide(local, conversation, arriving);
610
+ if (arriving.length > 0 || sealing || whole) {
611
+ deps.publish(conversationChangeSet([conversation]));
421
612
  }
422
613
  });
423
614
  }
@@ -426,11 +617,8 @@ export function createConversationStore(deps) {
426
617
  if (event.kind === 'gap') {
427
618
  if (replicated.has(conversation))
428
619
  gap(conversation);
429
- const held = lives.get(conversation);
430
- if (held === undefined)
431
- return;
432
- setLive(conversation, { ...QUIET, turn: held.turn });
433
- emit(conversation, false);
620
+ if (lives.has(conversation))
621
+ unsent.set(conversation, new Set(FACTS));
434
622
  return;
435
623
  }
436
624
  if (event.kind === 'turn') {
@@ -453,31 +641,147 @@ export function createConversationStore(deps) {
453
641
  live(conversation, { options: structuredClone(event.options) });
454
642
  return;
455
643
  }
644
+ if (event.kind === 'starting') {
645
+ live(conversation, { starting: event.step });
646
+ return;
647
+ }
648
+ if (event.kind === 'link') {
649
+ live(conversation, { link: { connected: event.connected } });
650
+ return;
651
+ }
652
+ if (event.kind === 'deliveries') {
653
+ live(conversation, { deliveryUnknown: [...event.unknown] });
654
+ return;
655
+ }
456
656
  const arriving = acceptedMessage(event.message);
457
657
  if (arriving === null)
458
658
  return;
459
659
  if (replicated.has(conversation)) {
460
- void keep(conversation, arriving).catch(() => { });
660
+ if (deps.full()) {
661
+ gap(conversation);
662
+ return;
663
+ }
664
+ void keep(conversation, arriving).catch((error) => {
665
+ if (refusedForSpace(error))
666
+ gap(conversation);
667
+ });
461
668
  return;
462
669
  }
463
670
  if (sealTurns(conversation, new Set([arriving.turn])))
464
671
  emit(conversation);
465
672
  }
673
+ async function kept(local, conversation, following) {
674
+ if (following === null || empty(following)) {
675
+ await local.dropChatDraft(conversation);
676
+ drafts.delete(conversation);
677
+ }
678
+ else {
679
+ await local.keepChatDrafts([following]);
680
+ drafts.set(conversation, following);
681
+ }
682
+ }
683
+ async function sealed(local, conversation, turns) {
684
+ const held = drafts.get(conversation);
685
+ const submitted = held?.submitted ?? [];
686
+ const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
687
+ if (held === undefined || left.length === submitted.length)
688
+ return false;
689
+ await kept(local, conversation, withSubmitted(held, left));
690
+ return true;
691
+ }
692
+ function moved(agent, operation, to) {
693
+ const from = newChat(agent);
694
+ return deps.durably(async (local) => {
695
+ const held = drafts.get(from);
696
+ const earlier = (held?.moves ?? []).filter((one) => !deps.expired(one));
697
+ if (earlier.includes(operation))
698
+ return;
699
+ const blank = {
700
+ conversation: from,
701
+ text: '',
702
+ revision: chatDraftRevision(),
703
+ };
704
+ const { moves: _answered, ...unsent } = held ?? blank;
705
+ const taken = drafts.has(to) || empty(unsent) ? [] : [{ ...unsent, conversation: to }];
706
+ const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
707
+ await local.keepChatDrafts([left, ...taken]);
708
+ drafts.set(from, left);
709
+ for (const one of taken)
710
+ drafts.set(to, one);
711
+ deps.publish(conversationChangeSet([from, to]));
712
+ });
713
+ }
466
714
  function withDraft(conversation, next) {
467
715
  return deps.durably(async (local) => {
468
- const following = next(drafts.get(conversation));
716
+ const following = await next(drafts.get(conversation), local);
469
717
  if (following === undefined)
470
718
  return;
471
- if (following === null) {
472
- await local.dropChatDraft(conversation);
473
- drafts.delete(conversation);
474
- }
475
- else {
476
- await local.keepChatDraft(following);
477
- drafts.set(conversation, following);
478
- }
479
- deps.publish(conversationChangeSet([], [conversation]));
719
+ await kept(local, conversation, following);
720
+ deps.publish(conversationChangeSet([conversation]));
721
+ });
722
+ }
723
+ async function submitted(conversation, operation, draft) {
724
+ let submission;
725
+ let earlier = false;
726
+ await withDraft(conversation, (current) => {
727
+ submission = current?.submitted?.find((one) => one.operation === operation);
728
+ earlier = submission !== undefined;
729
+ if (submission !== undefined)
730
+ return undefined;
731
+ const said = draft ?? current;
732
+ submission = { operation, text: said?.text ?? '', turn: null };
733
+ return {
734
+ conversation,
735
+ ...(draft === undefined || current === undefined
736
+ ? { text: '', revision: chatDraftRevision() }
737
+ : { text: current.text, revision: current.revision }),
738
+ submitted: [...(current?.submitted ?? []), submission],
739
+ };
480
740
  });
741
+ return { submission: submission, earlier };
742
+ }
743
+ function forgotten(conversation, submission) {
744
+ return withDraft(conversation, (current) => {
745
+ const held = current?.submitted ?? [];
746
+ if (current === undefined || !held.includes(submission))
747
+ return undefined;
748
+ return withSubmitted(current, held.filter((one) => one !== submission));
749
+ });
750
+ }
751
+ async function dispatched(conversation, submission) {
752
+ const dispatch = { answered: false };
753
+ sending.set(submission.operation, dispatch);
754
+ try {
755
+ const result = await invoke('conversation.send', conversation, {
756
+ text: submission.text,
757
+ operation_id: submission.operation,
758
+ });
759
+ dispatch.answered = result.kind !== 'transport-uncertainty';
760
+ if (result.kind === 'revoked-authorization')
761
+ return result;
762
+ if (result.kind !== 'action' && result.retryable) {
763
+ deps.unanswered();
764
+ return result;
765
+ }
766
+ const turn = answered(result, 'turn');
767
+ await withDraft(conversation, async (current, local) => {
768
+ const held = current?.submitted?.find((one) => one.operation === submission.operation);
769
+ if (current === undefined || held === undefined || held.turn !== null)
770
+ return undefined;
771
+ const others = current.submitted.filter((one) => one !== held);
772
+ if (result.kind !== 'action')
773
+ return returned(current, held, others);
774
+ const arrived = turn !== null && (await local.turnArrived(conversation, turn));
775
+ return withSubmitted(current, turn === null || arrived
776
+ ? others
777
+ : current.submitted.map((one) => (one === held ? { ...held, turn } : one)));
778
+ });
779
+ return result;
780
+ }
781
+ finally {
782
+ if (sending.get(submission.operation) === dispatch)
783
+ sending.delete(submission.operation);
784
+ }
481
785
  }
482
786
  function invoke(name, conversation, extra = {}) {
483
787
  return deps.call(name, { conversation, ...extra });
@@ -488,47 +792,48 @@ export function createConversationStore(deps) {
488
792
  return entries();
489
793
  },
490
794
  async read(conversation, options = {}) {
491
- const limit = limitOf(options);
492
- return deps.durably(() => Promise.resolve(viewOf(conversation, limit)));
795
+ const asked = askedOf(options);
796
+ return deps.durably(async (local) => {
797
+ const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
798
+ const total = asked.total ? await local.messageCount(conversation) : null;
799
+ return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total)));
800
+ });
493
801
  },
494
- create(input) {
495
- return deps.call('conversation.create', input);
802
+ async create(input) {
803
+ const result = await deps.call('conversation.create', input);
804
+ const conversation = answered(result, 'conversation');
805
+ if (conversation === null)
806
+ return result;
807
+ if (!listing.has(conversation))
808
+ creations.add(conversation);
809
+ const creation = creationOf(input);
810
+ if (creation !== null)
811
+ await moved(creation.agent, creation.operation, conversation);
812
+ return result;
496
813
  },
497
- async send(conversation) {
498
- const draft = drafts.get(conversation);
499
- const pending = draft === undefined ? undefined : { draft };
500
- if (pending !== undefined) {
501
- sending.set(conversation, pending);
502
- deps.publish(conversationChangeSet([], [conversation]));
503
- }
504
- try {
505
- const files = await Promise.all((draft?.files ?? []).map(async (file) => ({
506
- ...(await declaredFile(file)),
507
- blob: file.blob,
508
- })));
509
- const result = await invoke('conversation.send', conversation, {
510
- text: draft?.text ?? '',
511
- files,
814
+ async send(conversation, operation = changeBatchIdentity(), draft) {
815
+ if (deps.full())
816
+ return DURABLE_WRITE;
817
+ if (deps.online()) {
818
+ const held = await submitted(conversation, operation, draft).catch((error) => {
819
+ if (refusedForSpace(error))
820
+ return null;
821
+ throw error;
512
822
  });
513
- const turn = turnOf(result);
514
- if (turn === null || draft === undefined)
515
- return result;
516
- await withDraft(conversation, (current) => {
517
- if (current !== draft)
518
- return undefined;
519
- const sealed = (messages.get(conversation) ?? []).some((message) => message.turn === turn);
520
- return sealed ? null : { ...draft, turn };
521
- });
522
- return result;
823
+ if (held === null)
824
+ return DURABLE_WRITE;
825
+ const { submission, earlier } = held;
826
+ if (!earlier || !deps.expired(operation))
827
+ return dispatched(conversation, submission);
828
+ await forgotten(conversation, submission);
829
+ return EXPIRED;
523
830
  }
524
- finally {
525
- if (pending !== undefined && sending.get(conversation) === pending) {
526
- sending.delete(conversation);
527
- if (drafts.get(conversation) === draft) {
528
- deps.publish(conversationChangeSet([], [conversation]));
529
- }
530
- }
831
+ if (draft !== undefined) {
832
+ await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
833
+ ? undefined
834
+ : returned(current ?? { conversation, text: '', revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
531
835
  }
836
+ return OFFLINE;
532
837
  },
533
838
  answer(conversation, answer) {
534
839
  return invoke('conversation.answer', conversation, { answer });
@@ -543,7 +848,12 @@ export function createConversationStore(deps) {
543
848
  const result = await invoke('conversation.cancel', conversation, { turn });
544
849
  if (result.kind !== 'action')
545
850
  return result;
546
- await withDraft(conversation, (current) => current === undefined || current.turn !== turn ? undefined : { ...current, turn: null });
851
+ await withDraft(conversation, (current) => {
852
+ const held = current?.submitted?.find((one) => one.turn === turn);
853
+ if (current === undefined || held === undefined)
854
+ return undefined;
855
+ return returned(current, held, current.submitted.filter((one) => one !== held));
856
+ });
547
857
  return result;
548
858
  },
549
859
  delete(conversation) {
@@ -551,33 +861,44 @@ export function createConversationStore(deps) {
551
861
  },
552
862
  };
553
863
  const chatDraft = {
554
- write(conversation, draft) {
555
- return withDraft(conversation, () => ({
864
+ write(conversation, draft, revision = chatDraftRevision()) {
865
+ return withDraft(conversation, (current) => ({
556
866
  conversation,
557
867
  text: draft.text,
558
- files: [...draft.files],
559
- turn: null,
868
+ revision,
869
+ ...(current === undefined ? {} : pending(current)),
870
+ }));
871
+ },
872
+ prepend(conversation, draft) {
873
+ return withDraft(conversation, (current) => ({
874
+ conversation,
875
+ ...(current === undefined ? { text: draft.text } : prepended(current, draft)),
876
+ revision: chatDraftRevision(),
877
+ ...(current === undefined ? {} : pending(current)),
560
878
  }));
561
879
  },
562
880
  discard(conversation) {
563
- return withDraft(conversation, (current) => (current === undefined ? undefined : null));
881
+ return withDraft(conversation, (current) => current === undefined
882
+ ? undefined
883
+ : { conversation, text: '', revision: chatDraftRevision(), ...pending(current) });
564
884
  },
565
885
  };
566
886
  return {
567
887
  surface,
568
888
  chatDraft,
569
- hydrate(held, stored) {
570
- messages.clear();
889
+ hydrate(held, kept) {
890
+ stored.clear();
571
891
  reads.clear();
572
- for (const view of views.values())
573
- view.messages = null;
892
+ partials.clear();
574
893
  for (const entry of held) {
575
- messages.set(entry.conversation, [...entry.messages].sort(ordered));
894
+ stored.add(entry.conversation);
576
895
  if (entry.read !== undefined)
577
896
  reads.set(entry.conversation, entry.read);
897
+ if (entry.partial)
898
+ partials.add(entry.conversation);
578
899
  }
579
900
  drafts.clear();
580
- for (const record of stored)
901
+ for (const record of kept)
581
902
  drafts.set(record.conversation, record);
582
903
  },
583
904
  async reconcile() {
@@ -585,8 +906,10 @@ export function createConversationStore(deps) {
585
906
  return;
586
907
  const named = new Set(listing.keys());
587
908
  const leaving = [
588
- ...new Set([...replicated, ...messages.keys(), ...reads.keys(), ...drafts.keys()]),
589
- ].filter((conversation) => !named.has(conversation));
909
+ ...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
910
+ ].filter((conversation) => !named.has(conversation) &&
911
+ !creations.has(conversation) &&
912
+ (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
590
913
  for (const conversation of named) {
591
914
  if (!replicated.has(conversation))
592
915
  behind.add(conversation);
@@ -597,41 +920,100 @@ export function createConversationStore(deps) {
597
920
  await remove(leaving);
598
921
  },
599
922
  async catchUp(progress) {
600
- const reading = [...behind].filter((conversation) => replicated.has(conversation)).sort();
601
- if (reading.length > 0 && !deps.closed()) {
602
- const failed = await readHistories(reading);
923
+ if (deps.full()) {
924
+ if (behind.size > 0)
925
+ progress.complete = false;
926
+ return;
927
+ }
928
+ const owed = (conversation) => behind.has(conversation) && replicated.has(conversation);
929
+ const read = async (conversations) => {
930
+ if (conversations.length === 0 || deps.closed())
931
+ return;
932
+ const failed = await caughtUp(conversations);
603
933
  if (failed?.kind === 'transport-uncertainty')
604
934
  progress.reachable = false;
605
- }
935
+ };
936
+ const asking = [...behind].filter(owed).sort();
937
+ const awaited = asking.filter((conversation) => catching.has(conversation));
938
+ await Promise.all([
939
+ read(asking.filter((conversation) => !catching.has(conversation))),
940
+ Promise.all(awaited.map((conversation) => catching.get(conversation))).then(() => read(awaited.filter((conversation) => owed(conversation) && !catching.has(conversation)))),
941
+ ]);
606
942
  if (behind.size > 0)
607
943
  progress.complete = false;
608
944
  },
945
+ async resend(progress) {
946
+ for (const [conversation, held] of [...drafts]) {
947
+ for (const submission of held.submitted ?? []) {
948
+ if (submission.turn !== null || sending.has(submission.operation))
949
+ continue;
950
+ if (deps.closed())
951
+ return;
952
+ if (deps.expired(submission.operation)) {
953
+ await forgotten(conversation, submission);
954
+ continue;
955
+ }
956
+ const result = await dispatched(conversation, submission);
957
+ if (result.kind === 'revoked-authorization')
958
+ return;
959
+ if (result.kind === 'transport-uncertainty') {
960
+ progress.reachable = false;
961
+ return;
962
+ }
963
+ if (result.kind !== 'action' && result.retryable) {
964
+ progress.complete = false;
965
+ return;
966
+ }
967
+ }
968
+ }
969
+ },
609
970
  current() {
610
971
  return behind.size === 0;
611
972
  },
973
+ known() {
974
+ return listKnown;
975
+ },
612
976
  reconnected() {
613
977
  for (const conversation of replicated)
614
978
  gap(conversation);
615
979
  },
980
+ reopened() {
981
+ for (const [operation, dispatch] of sending)
982
+ if (!dispatch.answered)
983
+ sending.delete(operation);
984
+ },
985
+ lost() {
986
+ catching.clear();
987
+ },
616
988
  watch(conversation, options, deliver) {
617
- const limit = limitOf(options);
618
- const key = JSON.stringify([conversation, limit ?? null]);
989
+ const asked = askedOf(options);
990
+ const key = JSON.stringify([conversation, asked.limit, asked.after ?? null, asked.total]);
619
991
  const view = views.get(key) ?? {
620
992
  conversation,
621
- limit,
993
+ asked,
622
994
  watchers: new Set(),
623
- messages: null,
995
+ window: null,
996
+ more: false,
997
+ total: null,
624
998
  };
625
999
  views.set(key, view);
626
1000
  const watcher = { notify: deliver, ready: false };
627
1001
  view.watchers.add(watcher);
628
1002
  void deps
629
- .durably(() => Promise.resolve())
1003
+ .durably(async (local) => {
1004
+ if (view.window !== null)
1005
+ return;
1006
+ const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
1007
+ view.window = messages;
1008
+ view.more = more;
1009
+ view.total = asked.total ? await local.messageCount(conversation) : null;
1010
+ })
630
1011
  .then(() => {
631
1012
  if (deps.closed() || !view.watchers.has(watcher))
632
1013
  return;
633
1014
  watcher.ready = true;
634
- watcher.notify(viewOf(conversation, limit));
1015
+ const window = view.window ?? [];
1016
+ watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
635
1017
  })
636
1018
  .catch(() => { });
637
1019
  return () => {
@@ -651,6 +1033,16 @@ export function createConversationStore(deps) {
651
1033
  listWatchers.delete(deliver);
652
1034
  };
653
1035
  },
1036
+ watchAgents(deliver) {
1037
+ agentWatchers.add(deliver);
1038
+ void agentsReady().then(() => {
1039
+ if (!deps.closed() && agentWatchers.has(deliver))
1040
+ deliver(agents());
1041
+ });
1042
+ return () => {
1043
+ agentWatchers.delete(deliver);
1044
+ };
1045
+ },
654
1046
  changed(set) {
655
1047
  for (const conversation of set.conversations)
656
1048
  emit(conversation);
@@ -668,12 +1060,16 @@ export function createConversationStore(deps) {
668
1060
  if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
669
1061
  if (event.kind === 'list') {
670
1062
  listing.clear();
671
- for (const entry of event.entries)
1063
+ for (const entry of event.entries) {
672
1064
  listing.set(entry.conversation, { ...entry });
1065
+ creations.delete(entry.conversation);
1066
+ }
673
1067
  listKnown = true;
1068
+ openingEnded();
674
1069
  }
675
1070
  else if (event.kind === 'listed') {
676
1071
  listing.set(event.entry.conversation, { ...event.entry });
1072
+ creations.delete(event.entry.conversation);
677
1073
  }
678
1074
  else {
679
1075
  listing.delete(event.conversation);
@@ -687,17 +1083,18 @@ export function createConversationStore(deps) {
687
1083
  heard(event);
688
1084
  return false;
689
1085
  },
690
- messages() {
691
- return [...messages].flatMap(([conversation, held]) => held.map(({ identity, text }) => ({ conversation, identity, text })));
1086
+ searchable() {
1087
+ return [...(listKnown ? listing.keys() : stored)].sort();
692
1088
  },
693
- message(conversation, identity) {
694
- const held = messages.get(conversation)?.find((message) => message.identity === identity);
695
- return held === undefined ? null : { conversation, identity, text: held.text };
1089
+ acquired(conversation) {
1090
+ return reads.has(conversation) && !partials.has(conversation);
696
1091
  },
697
1092
  close() {
698
1093
  views.clear();
699
1094
  lives.clear();
1095
+ unsent.clear();
700
1096
  listWatchers.clear();
1097
+ agentWatchers.clear();
701
1098
  },
702
1099
  };
703
1100
  }