@agentshouse/mdruntime 0.1.0-c5d3fa6fb67bc → 0.1.0-c5f333dfa0583

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 (198) hide show
  1. package/AGENTS.md +225 -212
  2. package/README.md +9 -8
  3. package/dist/adapter.d.ts +64 -24
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/attached-model.d.ts +4 -1
  6. package/dist/attached-model.d.ts.map +1 -1
  7. package/dist/attached-model.js +13 -3
  8. package/dist/bootstrap.d.ts +15 -7
  9. package/dist/bootstrap.d.ts.map +1 -1
  10. package/dist/bootstrap.js +90 -61
  11. package/dist/chain.d.ts +11 -0
  12. package/dist/chain.d.ts.map +1 -0
  13. package/dist/chain.js +64 -0
  14. package/dist/change-set.d.ts +8 -9
  15. package/dist/change-set.d.ts.map +1 -1
  16. package/dist/change-set.js +10 -85
  17. package/dist/change.d.ts +31 -1
  18. package/dist/change.d.ts.map +1 -1
  19. package/dist/change.js +10 -1
  20. package/dist/chat/earlier.d.ts +10 -0
  21. package/dist/chat/earlier.d.ts.map +1 -0
  22. package/dist/chat/earlier.js +54 -0
  23. package/dist/chat/house.d.ts +25 -0
  24. package/dist/chat/house.d.ts.map +1 -0
  25. package/dist/chat/house.js +38 -0
  26. package/dist/chat/input.d.ts +3 -1
  27. package/dist/chat/input.d.ts.map +1 -1
  28. package/dist/chat/input.js +11 -18
  29. package/dist/chat/kit.js +1 -1
  30. package/dist/chat/list.d.ts +2 -1
  31. package/dist/chat/list.d.ts.map +1 -1
  32. package/dist/chat/list.js +45 -5
  33. package/dist/chat/markdown.d.ts +1 -0
  34. package/dist/chat/markdown.d.ts.map +1 -1
  35. package/dist/chat/markdown.js +78 -20
  36. package/dist/chat/message.d.ts +1 -0
  37. package/dist/chat/message.d.ts.map +1 -1
  38. package/dist/chat/message.js +73 -11
  39. package/dist/chat/messages.d.ts +14 -1
  40. package/dist/chat/messages.d.ts.map +1 -1
  41. package/dist/chat/messages.js +79 -24
  42. package/dist/chat/name.d.ts +1 -0
  43. package/dist/chat/name.d.ts.map +1 -1
  44. package/dist/chat/name.js +1 -1
  45. package/dist/chat/opened.d.ts +1 -0
  46. package/dist/chat/opened.d.ts.map +1 -1
  47. package/dist/chat/opened.js +2 -0
  48. package/dist/chat/overlay.d.ts +1 -0
  49. package/dist/chat/overlay.d.ts.map +1 -1
  50. package/dist/chat/overlay.js +11 -5
  51. package/dist/chat/parts.d.ts +1 -0
  52. package/dist/chat/parts.d.ts.map +1 -1
  53. package/dist/chat/parts.js +3 -2
  54. package/dist/chat/pieces.d.ts +62 -0
  55. package/dist/chat/pieces.d.ts.map +1 -0
  56. package/dist/chat/pieces.js +1110 -0
  57. package/dist/chat/provider.d.ts +8 -1
  58. package/dist/chat/provider.d.ts.map +1 -1
  59. package/dist/chat/provider.js +98 -92
  60. package/dist/chat/question.d.ts +1 -1
  61. package/dist/chat/question.d.ts.map +1 -1
  62. package/dist/chat/question.js +7 -18
  63. package/dist/chat/ready.d.ts +5 -2
  64. package/dist/chat/ready.d.ts.map +1 -1
  65. package/dist/chat/ready.js +7 -5
  66. package/dist/chat/room-file.d.ts +8 -0
  67. package/dist/chat/room-file.d.ts.map +1 -0
  68. package/dist/chat/room-file.js +72 -0
  69. package/dist/chat/steady.d.ts +3 -0
  70. package/dist/chat/steady.d.ts.map +1 -0
  71. package/dist/chat/steady.js +26 -0
  72. package/dist/chat/style.d.ts.map +1 -1
  73. package/dist/chat/style.js +6 -1
  74. package/dist/chat.d.ts +3 -2
  75. package/dist/chat.d.ts.map +1 -1
  76. package/dist/chat.js +2 -2
  77. package/dist/client.d.ts +11 -2
  78. package/dist/client.d.ts.map +1 -1
  79. package/dist/client.js +45 -7
  80. package/dist/collection.d.ts +6 -19
  81. package/dist/collection.d.ts.map +1 -1
  82. package/dist/collection.js +30 -287
  83. package/dist/compile.d.ts +11 -0
  84. package/dist/compile.d.ts.map +1 -0
  85. package/dist/compile.js +712 -0
  86. package/dist/connection.d.ts +21 -8
  87. package/dist/connection.d.ts.map +1 -1
  88. package/dist/connection.js +2319 -688
  89. package/dist/conversation.d.ts +15 -3
  90. package/dist/conversation.d.ts.map +1 -1
  91. package/dist/conversation.js +233 -80
  92. package/dist/digest.d.ts +7 -0
  93. package/dist/digest.d.ts.map +1 -0
  94. package/dist/digest.js +113 -0
  95. package/dist/durability.d.ts +2 -1
  96. package/dist/durability.d.ts.map +1 -1
  97. package/dist/entry.d.ts +3 -1
  98. package/dist/entry.d.ts.map +1 -1
  99. package/dist/entry.js +18 -23
  100. package/dist/file.d.ts +2 -10
  101. package/dist/file.d.ts.map +1 -1
  102. package/dist/file.js +3 -120
  103. package/dist/host.d.ts +5 -4
  104. package/dist/host.d.ts.map +1 -1
  105. package/dist/host.js +55 -7
  106. package/dist/index.d.ts +10 -6
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +4 -0
  109. package/dist/live.d.ts +22 -0
  110. package/dist/live.d.ts.map +1 -0
  111. package/dist/live.js +123 -0
  112. package/dist/memory.d.ts +7 -3
  113. package/dist/memory.d.ts.map +1 -1
  114. package/dist/memory.js +39 -17
  115. package/dist/name.d.ts +4 -0
  116. package/dist/name.d.ts.map +1 -0
  117. package/dist/name.js +122 -0
  118. package/dist/observation.d.ts +14 -0
  119. package/dist/observation.d.ts.map +1 -1
  120. package/dist/observation.js +93 -3
  121. package/dist/opened.d.ts +22 -0
  122. package/dist/opened.d.ts.map +1 -0
  123. package/dist/opened.js +107 -0
  124. package/dist/outcome.d.ts +35 -4
  125. package/dist/outcome.d.ts.map +1 -1
  126. package/dist/outcome.js +28 -0
  127. package/dist/overlay.d.ts +2 -5
  128. package/dist/overlay.d.ts.map +1 -1
  129. package/dist/overlay.js +86 -171
  130. package/dist/query.d.ts +26 -2
  131. package/dist/query.d.ts.map +1 -1
  132. package/dist/query.js +34 -6
  133. package/dist/reach.d.ts +5 -1
  134. package/dist/reach.d.ts.map +1 -1
  135. package/dist/reach.js +167 -48
  136. package/dist/react.d.ts +3 -3
  137. package/dist/react.d.ts.map +1 -1
  138. package/dist/react.js +7 -17
  139. package/dist/readiness.d.ts +1 -1
  140. package/dist/readiness.d.ts.map +1 -1
  141. package/dist/reads.d.ts +11 -0
  142. package/dist/reads.d.ts.map +1 -0
  143. package/dist/reads.js +98 -0
  144. package/dist/recovery.d.ts +2 -2
  145. package/dist/recovery.d.ts.map +1 -1
  146. package/dist/recovery.js +8 -7
  147. package/dist/refresh.d.ts +12 -15
  148. package/dist/refresh.d.ts.map +1 -1
  149. package/dist/refresh.js +74 -51
  150. package/dist/replica.d.ts +55 -33
  151. package/dist/replica.d.ts.map +1 -1
  152. package/dist/replica.js +295 -186
  153. package/dist/room-draft.d.ts +15 -1
  154. package/dist/room-draft.d.ts.map +1 -1
  155. package/dist/room-draft.js +32 -25
  156. package/dist/rows.d.ts +29 -30
  157. package/dist/rows.d.ts.map +1 -1
  158. package/dist/rows.js +113 -64
  159. package/dist/save.d.ts +51 -14
  160. package/dist/save.d.ts.map +1 -1
  161. package/dist/save.js +518 -180
  162. package/dist/search.d.ts +19 -4
  163. package/dist/search.d.ts.map +1 -1
  164. package/dist/search.js +192 -35
  165. package/dist/seed.d.ts +16 -0
  166. package/dist/seed.d.ts.map +1 -0
  167. package/dist/seed.js +75 -0
  168. package/dist/shared.d.ts +8 -3
  169. package/dist/shared.d.ts.map +1 -1
  170. package/dist/shared.js +129 -66
  171. package/dist/store.d.ts +99 -71
  172. package/dist/store.d.ts.map +1 -1
  173. package/dist/store.js +461 -586
  174. package/dist/stored.d.ts +143 -0
  175. package/dist/stored.d.ts.map +1 -0
  176. package/dist/stored.js +561 -0
  177. package/dist/tables.d.ts +320 -0
  178. package/dist/tables.d.ts.map +1 -0
  179. package/dist/tables.js +537 -0
  180. package/dist/turn.d.ts +5 -3
  181. package/dist/turn.d.ts.map +1 -1
  182. package/dist/turn.js +51 -21
  183. package/dist/validation.d.ts +30 -0
  184. package/dist/validation.d.ts.map +1 -0
  185. package/dist/validation.js +162 -0
  186. package/dist/windows.d.ts +22 -0
  187. package/dist/windows.d.ts.map +1 -0
  188. package/dist/windows.js +89 -0
  189. package/dist/wire.d.ts +32 -0
  190. package/dist/wire.d.ts.map +1 -1
  191. package/dist/wire.js +8 -0
  192. package/package.json +10 -3
  193. package/dist/acquisition.d.ts +0 -14
  194. package/dist/acquisition.d.ts.map +0 -1
  195. package/dist/acquisition.js +0 -24
  196. package/dist/read-side.d.ts +0 -25
  197. package/dist/read-side.d.ts.map +0 -1
  198. package/dist/read-side.js +0 -642
@@ -3,13 +3,26 @@ import { runtimeError } from './failure.js';
3
3
  import { declaredFile } from './file.js';
4
4
  import { changeBatchIdentity, chatDraftRevision } from './identity.js';
5
5
  import { OFFLINE } from './outcome.js';
6
+ import { capped, cursorOf, decodedCursor, FROM_START, messageOrder, precedes } from './windows.js';
6
7
  const QUIET = {
7
8
  turn: null,
8
9
  questions: [],
9
10
  process: null,
10
11
  commands: [],
11
12
  options: [],
13
+ starting: null,
14
+ link: null,
15
+ deliveryUnknown: [],
12
16
  };
17
+ const FACTS = [
18
+ 'questions',
19
+ 'process',
20
+ 'commands',
21
+ 'options',
22
+ 'starting',
23
+ 'link',
24
+ 'deliveryUnknown',
25
+ ];
13
26
  export function unheldView(conversation) {
14
27
  return {
15
28
  conversation,
@@ -19,6 +32,9 @@ export function unheldView(conversation) {
19
32
  process: null,
20
33
  commands: [],
21
34
  options: [],
35
+ starting: null,
36
+ link: null,
37
+ deliveryUnknown: [],
22
38
  chatDraft: null,
23
39
  };
24
40
  }
@@ -30,8 +46,10 @@ export function newChat(agent) {
30
46
  function creationOf(input) {
31
47
  if (typeof input !== 'object' || input === null)
32
48
  return null;
33
- const { agent_id: agent, operation_id: operation } = input;
34
- return typeof agent === 'string' && typeof operation === 'string' ? { agent, operation } : null;
49
+ const { agent_id: agent, operation_id: operation, replaces, } = input;
50
+ return typeof agent === 'string' && typeof operation === 'string' && replaces === undefined
51
+ ? { agent, operation }
52
+ : null;
35
53
  }
36
54
  function startedTurn(turn, operation) {
37
55
  if (operation.op === 'begin')
@@ -82,19 +100,25 @@ function appliedTurn(held, operation) {
82
100
  parts: held.parts.map((kept, at) => (at === operation.index ? changed : kept)),
83
101
  };
84
102
  }
85
- function ordered(left, right) {
86
- if (left.position !== right.position)
87
- return left.position < right.position ? -1 : 1;
88
- if (left.identity !== right.identity)
89
- return left.identity < right.identity ? -1 : 1;
90
- return 0;
91
- }
92
103
  function windowOf(held, arriving, limit) {
93
104
  const byIdentity = new Map(held.map((message) => [message.identity, message]));
94
105
  for (const message of arriving)
95
106
  byIdentity.set(message.identity, message);
96
- const next = [...byIdentity.values()].sort(ordered);
97
- return limit === undefined ? next : next.slice(Math.max(0, next.length - limit));
107
+ const next = [...byIdentity.values()].sort(messageOrder);
108
+ const start = Math.max(0, next.length - limit);
109
+ return { messages: next.slice(start), dropped: start > 0 };
110
+ }
111
+ function frameOf(asked, messages, more, total) {
112
+ const oldest = messages[0];
113
+ const next = !more
114
+ ? undefined
115
+ : oldest === undefined
116
+ ? (asked.after ?? FROM_START)
117
+ : cursorOf([oldest.position, oldest.identity]);
118
+ return {
119
+ ...(next === undefined ? {} : { next }),
120
+ ...(total === null ? {} : { total }),
121
+ };
98
122
  }
99
123
  function newestOf(arriving, from) {
100
124
  return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
@@ -165,18 +189,28 @@ function answered(result, field) {
165
189
  const value = answer[field];
166
190
  return typeof value === 'string' ? value : null;
167
191
  }
168
- function limitOf(options) {
169
- const { limit } = options;
170
- if (limit === undefined)
171
- return undefined;
172
- if (!Number.isInteger(limit) || limit < 0) {
192
+ function beforeOf(after) {
193
+ if (after === undefined)
194
+ return null;
195
+ const decoded = typeof after === 'string' ? decodedCursor(after) : null;
196
+ if (decoded?.length === 0)
197
+ return null;
198
+ const [position, identity] = decoded ?? [];
199
+ if (decoded?.length !== 2 || typeof position !== 'string' || typeof identity !== 'string') {
200
+ throw runtimeError('invalid-after', `conversation after must be the next an earlier view returned, got ${String(after)}`);
201
+ }
202
+ return [position, identity];
203
+ }
204
+ function askedOf(options) {
205
+ const { limit, after, total } = options;
206
+ if (limit !== undefined && (!Number.isInteger(limit) || limit < 0)) {
173
207
  throw runtimeError('invalid-limit', `conversation limit must be a non-negative integer, got ${String(limit)}`);
174
208
  }
175
- return limit;
209
+ return { limit: capped(limit), after, before: beforeOf(after), total: total === true };
176
210
  }
177
211
  const EXPIRED = { kind: 'rejected', code: 'expired', fields: {}, retryable: false };
178
212
  function unwritten(draft) {
179
- return draft.text === '' && draft.files.length === 0;
213
+ return draft.text === '';
180
214
  }
181
215
  function pending(draft) {
182
216
  return {
@@ -191,11 +225,13 @@ function withSubmitted(draft, submitted) {
191
225
  const { submitted: _held, ...unsent } = draft;
192
226
  return submitted.length === 0 ? unsent : { ...unsent, submitted };
193
227
  }
228
+ function prepended(draft, said) {
229
+ return { text: [said.text, draft.text].filter((one) => one !== '').join('\n') };
230
+ }
194
231
  function returned(draft, submission, others) {
195
232
  return withSubmitted({
196
233
  conversation: draft.conversation,
197
- text: [submission.text, draft.text].filter((one) => one !== '').join('\n'),
198
- files: [...submission.files, ...draft.files],
234
+ ...prepended(draft, submission),
199
235
  revision: chatDraftRevision(),
200
236
  }, others);
201
237
  }
@@ -206,8 +242,10 @@ export function createConversationStore(deps) {
206
242
  const drafts = new Map();
207
243
  const sending = new Map();
208
244
  const behind = new Set();
245
+ const catching = new Map();
209
246
  const gaps = new Map();
210
247
  const lives = new Map();
248
+ const unsent = new Map();
211
249
  const views = new Map();
212
250
  const listing = new Map();
213
251
  const creations = new Set();
@@ -222,7 +260,7 @@ export function createConversationStore(deps) {
222
260
  function liveOf(conversation) {
223
261
  return lives.get(conversation) ?? QUIET;
224
262
  }
225
- function viewOf(conversation, held) {
263
+ function viewOf(conversation, held, frame) {
226
264
  const draft = drafts.get(conversation);
227
265
  const facts = liveOf(conversation);
228
266
  return {
@@ -233,14 +271,18 @@ export function createConversationStore(deps) {
233
271
  text,
234
272
  message,
235
273
  })),
274
+ ...frame,
236
275
  turn: structuredClone(facts.turn),
237
276
  questions: structuredClone(facts.questions),
238
277
  process: facts.process === null ? null : { ...facts.process },
239
278
  commands: structuredClone(facts.commands),
240
279
  options: structuredClone(facts.options),
280
+ starting: facts.starting,
281
+ link: facts.link === null ? null : { ...facts.link },
282
+ deliveryUnknown: [...facts.deliveryUnknown],
241
283
  chatDraft: draft === undefined || unwritten(draft)
242
284
  ? null
243
- : { text: draft.text, files: [...draft.files], revision: draft.revision },
285
+ : { text: draft.text, revision: draft.revision },
244
286
  };
245
287
  }
246
288
  function emit(conversation) {
@@ -252,16 +294,34 @@ export function createConversationStore(deps) {
252
294
  const ready = [...view.watchers].filter((watcher) => watcher.ready);
253
295
  if (ready.length === 0)
254
296
  continue;
255
- const value = viewOf(conversation, view.window);
297
+ const value = viewOf(conversation, view.window, sharedFrame(view, view.window));
256
298
  for (const watcher of ready)
257
299
  watcher.notify(value);
258
300
  }
259
301
  }
260
- function slide(conversation, arriving) {
302
+ function sharedFrame(view, window) {
303
+ return frameOf(view.asked, window, view.more, view.total);
304
+ }
305
+ async function slide(local, conversation, arriving) {
306
+ let count;
261
307
  for (const view of views.values()) {
262
308
  if (view.conversation !== conversation || view.window === null)
263
309
  continue;
264
- view.window = arriving === null ? [] : windowOf(view.window, arriving, view.limit);
310
+ if (arriving === null) {
311
+ view.window = [];
312
+ view.more = false;
313
+ if (view.total !== null)
314
+ view.total = 0;
315
+ continue;
316
+ }
317
+ const joining = arriving.filter((message) => precedes(message, view.asked.before));
318
+ const next = windowOf(view.window, joining, view.asked.limit);
319
+ view.window = next.messages;
320
+ view.more ||= next.dropped;
321
+ if (view.total !== null && arriving.length > 0) {
322
+ count ??= await local.messageCount(conversation);
323
+ view.total = count;
324
+ }
265
325
  }
266
326
  }
267
327
  function changesOf(conversation, arriving) {
@@ -281,6 +341,7 @@ export function createConversationStore(deps) {
281
341
  created: entry.created,
282
342
  finished: entry.finished,
283
343
  outcome: entry.outcome,
344
+ waiting: entry.waiting,
284
345
  state: stateOf(entry.conversation),
285
346
  started: lives.get(entry.conversation)?.turn?.started ?? null,
286
347
  }));
@@ -323,23 +384,30 @@ export function createConversationStore(deps) {
323
384
  const declaration = deps.agents();
324
385
  if (declaration === undefined)
325
386
  return;
326
- const held = await deps.documents(declaration);
327
- if ('pending' in held)
328
- return;
329
- const { documents } = held;
330
387
  const names = new Map();
331
388
  const retired = new Set();
332
- for (const document of documents) {
333
- if (document.type !== declaration.type)
334
- continue;
335
- const identity = document.fields?.[declaration.identity];
336
- const name = document.fields?.[declaration.name];
337
- if (typeof identity !== 'string' || typeof name !== 'string')
338
- continue;
339
- names.set(identity, name);
340
- const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
341
- if (ended !== undefined && ended !== null)
342
- retired.add(identity);
389
+ let covered = null;
390
+ for (let after;;) {
391
+ const read = await deps.documents(declaration, after);
392
+ if ('pending' in read)
393
+ return;
394
+ const coverage = JSON.stringify(read.coverage);
395
+ if (covered !== null && coverage !== covered)
396
+ return readAgents();
397
+ covered = coverage;
398
+ for (const document of read.documents) {
399
+ const identity = document.fields?.[declaration.identity];
400
+ const name = document.fields?.[declaration.name];
401
+ if (typeof identity !== 'string' || typeof name !== 'string')
402
+ continue;
403
+ names.set(identity, name);
404
+ const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
405
+ if (ended !== undefined && ended !== null)
406
+ retired.add(identity);
407
+ }
408
+ if (read.next === undefined)
409
+ break;
410
+ after = read.next;
343
411
  }
344
412
  const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
345
413
  if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents]))
@@ -363,9 +431,34 @@ export function createConversationStore(deps) {
363
431
  return agentsRead;
364
432
  }
365
433
  function live(conversation, change) {
434
+ const owed = unsent.get(conversation);
435
+ for (const fact of FACTS.filter((one) => one in change))
436
+ owed?.delete(fact);
366
437
  setLive(conversation, { ...liveOf(conversation), ...change });
367
438
  emit(conversation);
368
439
  }
440
+ function openingEnded() {
441
+ const owed = [...unsent];
442
+ unsent.clear();
443
+ for (const [conversation, facts] of owed) {
444
+ const held = lives.get(conversation);
445
+ if (held === undefined || facts.size === 0)
446
+ continue;
447
+ setLive(conversation, {
448
+ turn: held.turn,
449
+ questions: facts.has('questions') ? QUIET.questions : held.questions,
450
+ process: facts.has('process') ? QUIET.process : held.process,
451
+ commands: facts.has('commands') ? QUIET.commands : held.commands,
452
+ options: facts.has('options') ? QUIET.options : held.options,
453
+ starting: facts.has('starting') ? QUIET.starting : held.starting,
454
+ link: facts.has('link') ? QUIET.link : held.link,
455
+ deliveryUnknown: facts.has('deliveryUnknown')
456
+ ? QUIET.deliveryUnknown
457
+ : held.deliveryUnknown,
458
+ });
459
+ emit(conversation);
460
+ }
461
+ }
369
462
  function relayed(conversation, turn, operation) {
370
463
  const held = liveOf(conversation).turn;
371
464
  const next = held?.turn === turn ? appliedTurn(held, operation) : startedTurn(turn, operation);
@@ -409,8 +502,9 @@ export function createConversationStore(deps) {
409
502
  partials.delete(conversation);
410
503
  drafts.delete(conversation);
411
504
  behind.delete(conversation);
505
+ unsent.delete(conversation);
412
506
  setLive(conversation, null);
413
- slide(conversation, null);
507
+ await slide(local, conversation, null);
414
508
  }
415
509
  if (changed.length > 0)
416
510
  deps.publish(conversationChangeSet(removed, changed));
@@ -438,11 +532,11 @@ export function createConversationStore(deps) {
438
532
  if (advances)
439
533
  reads.set(conversation, message.position);
440
534
  sealTurns(conversation, new Set([message.turn]));
441
- slide(conversation, [message]);
535
+ await slide(local, conversation, [message]);
442
536
  deps.publish(conversationChangeSet(changesOf(conversation, [message]), [conversation]));
443
537
  });
444
538
  }
445
- async function readHistories(conversations) {
539
+ async function readHistories(conversations, ended) {
446
540
  const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
447
541
  let asking = conversations.map((conversation) => ({
448
542
  conversation,
@@ -468,10 +562,33 @@ export function createConversationStore(deps) {
468
562
  following.push({ conversation: page.conversation, after: page.next });
469
563
  }
470
564
  }
565
+ for (const { conversation } of asking) {
566
+ if (!following.some((next) => next.conversation === conversation))
567
+ ended(conversation);
568
+ }
471
569
  asking = following;
472
570
  }
473
571
  return null;
474
572
  }
573
+ function caughtUp(conversations) {
574
+ const ends = new Map();
575
+ for (const conversation of conversations) {
576
+ const held = new Promise((end) => ends.set(conversation, end));
577
+ catching.set(conversation, held);
578
+ void held.then(() => {
579
+ if (catching.get(conversation) === held)
580
+ catching.delete(conversation);
581
+ });
582
+ }
583
+ const ended = (conversation) => ends.get(conversation)?.();
584
+ const read = readHistories(conversations, ended);
585
+ const endAll = () => {
586
+ for (const conversation of conversations)
587
+ ended(conversation);
588
+ };
589
+ read.then(endAll, endAll);
590
+ return read;
591
+ }
475
592
  function keepPage(conversation, arriving, from, next, generation) {
476
593
  return deps.durably(async (local) => {
477
594
  if (!replicated.has(conversation))
@@ -490,7 +607,7 @@ export function createConversationStore(deps) {
490
607
  sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
491
608
  if (whole && (gaps.get(conversation) ?? 0) === generation)
492
609
  behind.delete(conversation);
493
- slide(conversation, arriving);
610
+ await slide(local, conversation, arriving);
494
611
  if (arriving.length > 0 || sealing || whole) {
495
612
  deps.publish(conversationChangeSet(changesOf(conversation, arriving), [conversation]));
496
613
  }
@@ -501,11 +618,8 @@ export function createConversationStore(deps) {
501
618
  if (event.kind === 'gap') {
502
619
  if (replicated.has(conversation))
503
620
  gap(conversation);
504
- const held = lives.get(conversation);
505
- if (held === undefined)
506
- return;
507
- setLive(conversation, { ...QUIET, turn: held.turn });
508
- emit(conversation);
621
+ if (lives.has(conversation))
622
+ unsent.set(conversation, new Set(FACTS));
509
623
  return;
510
624
  }
511
625
  if (event.kind === 'turn') {
@@ -528,6 +642,18 @@ export function createConversationStore(deps) {
528
642
  live(conversation, { options: structuredClone(event.options) });
529
643
  return;
530
644
  }
645
+ if (event.kind === 'starting') {
646
+ live(conversation, { starting: event.step });
647
+ return;
648
+ }
649
+ if (event.kind === 'link') {
650
+ live(conversation, { link: { connected: event.connected } });
651
+ return;
652
+ }
653
+ if (event.kind === 'deliveries') {
654
+ live(conversation, { deliveryUnknown: [...event.unknown] });
655
+ return;
656
+ }
531
657
  const arriving = acceptedMessage(event.message);
532
658
  if (arriving === null)
533
659
  return;
@@ -567,7 +693,6 @@ export function createConversationStore(deps) {
567
693
  const blank = {
568
694
  conversation: from,
569
695
  text: '',
570
- files: [],
571
696
  revision: chatDraftRevision(),
572
697
  };
573
698
  const { moves: _answered, ...unsent } = held ?? blank;
@@ -598,17 +723,12 @@ export function createConversationStore(deps) {
598
723
  if (submission !== undefined)
599
724
  return undefined;
600
725
  const said = draft ?? current;
601
- submission = {
602
- operation,
603
- text: said?.text ?? '',
604
- files: [...(said?.files ?? [])],
605
- turn: null,
606
- };
726
+ submission = { operation, text: said?.text ?? '', turn: null };
607
727
  return {
608
728
  conversation,
609
729
  ...(draft === undefined || current === undefined
610
- ? { text: '', files: [], revision: chatDraftRevision() }
611
- : { text: current.text, files: current.files, revision: current.revision }),
730
+ ? { text: '', revision: chatDraftRevision() }
731
+ : { text: current.text, revision: current.revision }),
612
732
  submitted: [...(current?.submitted ?? []), submission],
613
733
  };
614
734
  });
@@ -626,10 +746,8 @@ export function createConversationStore(deps) {
626
746
  const dispatch = {};
627
747
  sending.set(submission.operation, dispatch);
628
748
  try {
629
- const files = await Promise.all(submission.files.map(async (file) => ({ ...(await declaredFile(file)), blob: file.blob })));
630
749
  const result = await invoke('conversation.send', conversation, {
631
750
  text: submission.text,
632
- files,
633
751
  operation_id: submission.operation,
634
752
  });
635
753
  if (result.kind === 'revoked-authorization')
@@ -667,8 +785,12 @@ export function createConversationStore(deps) {
667
785
  return entries();
668
786
  },
669
787
  async read(conversation, options = {}) {
670
- const limit = limitOf(options);
671
- return deps.durably(async (local) => structuredClone(viewOf(conversation, await local.messageWindow(conversation, limit))));
788
+ const asked = askedOf(options);
789
+ return deps.durably(async (local) => {
790
+ const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
791
+ const total = asked.total ? await local.messageCount(conversation) : null;
792
+ return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total)));
793
+ });
672
794
  },
673
795
  async create(input) {
674
796
  const result = await deps.call('conversation.create', input);
@@ -693,10 +815,19 @@ export function createConversationStore(deps) {
693
815
  if (draft !== undefined) {
694
816
  await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
695
817
  ? undefined
696
- : returned(current ?? { conversation, text: '', files: [], revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
818
+ : returned(current ?? { conversation, text: '', revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
697
819
  }
698
820
  return OFFLINE;
699
821
  },
822
+ async attach(room, file) {
823
+ if (!deps.online())
824
+ return OFFLINE;
825
+ return deps.call('conversation.attach', {
826
+ room,
827
+ ...(await declaredFile(file)),
828
+ blob: file.blob,
829
+ });
830
+ },
700
831
  answer(conversation, answer) {
701
832
  return invoke('conversation.answer', conversation, { answer });
702
833
  },
@@ -727,21 +858,22 @@ export function createConversationStore(deps) {
727
858
  return withDraft(conversation, (current) => ({
728
859
  conversation,
729
860
  text: draft.text,
730
- files: [...draft.files],
731
861
  revision,
732
862
  ...(current === undefined ? {} : pending(current)),
733
863
  }));
734
864
  },
865
+ prepend(conversation, draft) {
866
+ return withDraft(conversation, (current) => ({
867
+ conversation,
868
+ ...(current === undefined ? { text: draft.text } : prepended(current, draft)),
869
+ revision: chatDraftRevision(),
870
+ ...(current === undefined ? {} : pending(current)),
871
+ }));
872
+ },
735
873
  discard(conversation) {
736
874
  return withDraft(conversation, (current) => current === undefined
737
875
  ? undefined
738
- : {
739
- conversation,
740
- text: '',
741
- files: [],
742
- revision: chatDraftRevision(),
743
- ...pending(current),
744
- });
876
+ : { conversation, text: '', revision: chatDraftRevision(), ...pending(current) });
745
877
  },
746
878
  };
747
879
  return {
@@ -781,12 +913,20 @@ export function createConversationStore(deps) {
781
913
  await remove(leaving);
782
914
  },
783
915
  async catchUp(progress) {
784
- const reading = [...behind].filter((conversation) => replicated.has(conversation)).sort();
785
- if (reading.length > 0 && !deps.closed()) {
786
- const failed = await readHistories(reading);
916
+ const owed = (conversation) => behind.has(conversation) && replicated.has(conversation);
917
+ const read = async (conversations) => {
918
+ if (conversations.length === 0 || deps.closed())
919
+ return;
920
+ const failed = await caughtUp(conversations);
787
921
  if (failed?.kind === 'transport-uncertainty')
788
922
  progress.reachable = false;
789
- }
923
+ };
924
+ const asking = [...behind].filter(owed).sort();
925
+ const awaited = asking.filter((conversation) => catching.has(conversation));
926
+ await Promise.all([
927
+ read(asking.filter((conversation) => !catching.has(conversation))),
928
+ Promise.all(awaited.map((conversation) => catching.get(conversation))).then(() => read(awaited.filter((conversation) => owed(conversation) && !catching.has(conversation)))),
929
+ ]);
790
930
  if (behind.size > 0)
791
931
  progress.complete = false;
792
932
  },
@@ -828,27 +968,38 @@ export function createConversationStore(deps) {
828
968
  reopened() {
829
969
  sending.clear();
830
970
  },
971
+ lost() {
972
+ catching.clear();
973
+ },
831
974
  watch(conversation, options, deliver) {
832
- const limit = limitOf(options);
833
- const key = JSON.stringify([conversation, limit ?? null]);
975
+ const asked = askedOf(options);
976
+ const key = JSON.stringify([conversation, asked.limit, asked.after ?? null, asked.total]);
834
977
  const view = views.get(key) ?? {
835
978
  conversation,
836
- limit,
979
+ asked,
837
980
  watchers: new Set(),
838
981
  window: null,
982
+ more: false,
983
+ total: null,
839
984
  };
840
985
  views.set(key, view);
841
986
  const watcher = { notify: deliver, ready: false };
842
987
  view.watchers.add(watcher);
843
988
  void deps
844
989
  .durably(async (local) => {
845
- view.window ??= await local.messageWindow(conversation, limit);
990
+ if (view.window !== null)
991
+ return;
992
+ const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
993
+ view.window = messages;
994
+ view.more = more;
995
+ view.total = asked.total ? await local.messageCount(conversation) : null;
846
996
  })
847
997
  .then(() => {
848
998
  if (deps.closed() || !view.watchers.has(watcher))
849
999
  return;
850
1000
  watcher.ready = true;
851
- watcher.notify(viewOf(conversation, view.window ?? []));
1001
+ const window = view.window ?? [];
1002
+ watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
852
1003
  })
853
1004
  .catch(() => { });
854
1005
  return () => {
@@ -900,6 +1051,7 @@ export function createConversationStore(deps) {
900
1051
  creations.delete(entry.conversation);
901
1052
  }
902
1053
  listKnown = true;
1054
+ openingEnded();
903
1055
  }
904
1056
  else if (event.kind === 'listed') {
905
1057
  listing.set(event.entry.conversation, { ...event.entry });
@@ -926,6 +1078,7 @@ export function createConversationStore(deps) {
926
1078
  close() {
927
1079
  views.clear();
928
1080
  lives.clear();
1081
+ unsent.clear();
929
1082
  listWatchers.clear();
930
1083
  agentWatchers.clear();
931
1084
  },
@@ -0,0 +1,7 @@
1
+ export type Digest = {
2
+ update(bytes: Uint8Array): void;
3
+ hex(): string;
4
+ };
5
+ export declare function digest(): Digest;
6
+ export declare function sha256(blob: Blob): Promise<string>;
7
+ //# sourceMappingURL=digest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"AA0EA,MAAM,MAAM,MAAM,GAAG;IACnB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC,GAAG,IAAI,MAAM,CAAC;CACf,CAAC;AAEF,wBAAgB,MAAM,IAAI,MAAM,CAoC/B;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAMxD"}