@agentshouse/mdruntime 0.1.0-c5d3fa6fb67bc → 0.1.0-c63ad08fad0c3

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 (211) hide show
  1. package/AGENTS.md +240 -220
  2. package/README.md +9 -8
  3. package/dist/adapter.d.ts +72 -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 +26 -7
  9. package/dist/bootstrap.d.ts.map +1 -1
  10. package/dist/bootstrap.js +167 -82
  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 +9 -16
  15. package/dist/change-set.d.ts.map +1 -1
  16. package/dist/change-set.js +11 -89
  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 +16 -0
  84. package/dist/compile.d.ts.map +1 -0
  85. package/dist/compile.js +721 -0
  86. package/dist/connection.d.ts +22 -8
  87. package/dist/connection.d.ts.map +1 -1
  88. package/dist/connection.js +2509 -830
  89. package/dist/conversation.d.ts +15 -3
  90. package/dist/conversation.d.ts.map +1 -1
  91. package/dist/conversation.js +238 -94
  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 +3 -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/fulltext.d.ts +27 -0
  104. package/dist/fulltext.d.ts.map +1 -0
  105. package/dist/fulltext.js +368 -0
  106. package/dist/host.d.ts +5 -4
  107. package/dist/host.d.ts.map +1 -1
  108. package/dist/host.js +57 -7
  109. package/dist/identity.d.ts +0 -1
  110. package/dist/identity.d.ts.map +1 -1
  111. package/dist/identity.js +0 -3
  112. package/dist/index.d.ts +12 -8
  113. package/dist/index.d.ts.map +1 -1
  114. package/dist/index.js +4 -0
  115. package/dist/live.d.ts +22 -0
  116. package/dist/live.d.ts.map +1 -0
  117. package/dist/live.js +123 -0
  118. package/dist/memory.d.ts +7 -3
  119. package/dist/memory.d.ts.map +1 -1
  120. package/dist/memory.js +39 -17
  121. package/dist/messages.d.ts +28 -0
  122. package/dist/messages.d.ts.map +1 -0
  123. package/dist/messages.js +90 -0
  124. package/dist/name.d.ts +4 -0
  125. package/dist/name.d.ts.map +1 -0
  126. package/dist/name.js +122 -0
  127. package/dist/observation.d.ts +14 -0
  128. package/dist/observation.d.ts.map +1 -1
  129. package/dist/observation.js +93 -3
  130. package/dist/opened.d.ts +22 -0
  131. package/dist/opened.d.ts.map +1 -0
  132. package/dist/opened.js +107 -0
  133. package/dist/outcome.d.ts +35 -4
  134. package/dist/outcome.d.ts.map +1 -1
  135. package/dist/outcome.js +28 -0
  136. package/dist/overlay.d.ts +2 -5
  137. package/dist/overlay.d.ts.map +1 -1
  138. package/dist/overlay.js +86 -171
  139. package/dist/query.d.ts +29 -2
  140. package/dist/query.d.ts.map +1 -1
  141. package/dist/query.js +46 -7
  142. package/dist/reach.d.ts +5 -1
  143. package/dist/reach.d.ts.map +1 -1
  144. package/dist/reach.js +167 -48
  145. package/dist/react.d.ts +3 -3
  146. package/dist/react.d.ts.map +1 -1
  147. package/dist/react.js +7 -17
  148. package/dist/readiness.d.ts +18 -3
  149. package/dist/readiness.d.ts.map +1 -1
  150. package/dist/readiness.js +1 -1
  151. package/dist/reads.d.ts +11 -0
  152. package/dist/reads.d.ts.map +1 -0
  153. package/dist/reads.js +100 -0
  154. package/dist/recovery.d.ts +2 -2
  155. package/dist/recovery.d.ts.map +1 -1
  156. package/dist/recovery.js +8 -7
  157. package/dist/refresh.d.ts +13 -15
  158. package/dist/refresh.d.ts.map +1 -1
  159. package/dist/refresh.js +78 -51
  160. package/dist/replica.d.ts +57 -33
  161. package/dist/replica.d.ts.map +1 -1
  162. package/dist/replica.js +295 -186
  163. package/dist/room-draft.d.ts +15 -1
  164. package/dist/room-draft.d.ts.map +1 -1
  165. package/dist/room-draft.js +32 -25
  166. package/dist/rows.d.ts +29 -30
  167. package/dist/rows.d.ts.map +1 -1
  168. package/dist/rows.js +113 -64
  169. package/dist/save.d.ts +51 -14
  170. package/dist/save.d.ts.map +1 -1
  171. package/dist/save.js +518 -180
  172. package/dist/search.d.ts +25 -42
  173. package/dist/search.d.ts.map +1 -1
  174. package/dist/search.js +30 -169
  175. package/dist/seed.d.ts +16 -0
  176. package/dist/seed.d.ts.map +1 -0
  177. package/dist/seed.js +75 -0
  178. package/dist/shared.d.ts +8 -3
  179. package/dist/shared.d.ts.map +1 -1
  180. package/dist/shared.js +129 -66
  181. package/dist/store.d.ts +97 -88
  182. package/dist/store.d.ts.map +1 -1
  183. package/dist/store.js +335 -659
  184. package/dist/stored.d.ts +145 -0
  185. package/dist/stored.d.ts.map +1 -0
  186. package/dist/stored.js +567 -0
  187. package/dist/tables.d.ts +342 -0
  188. package/dist/tables.d.ts.map +1 -0
  189. package/dist/tables.js +576 -0
  190. package/dist/turn.d.ts +5 -3
  191. package/dist/turn.d.ts.map +1 -1
  192. package/dist/turn.js +51 -21
  193. package/dist/validation.d.ts +30 -0
  194. package/dist/validation.d.ts.map +1 -0
  195. package/dist/validation.js +162 -0
  196. package/dist/windows.d.ts +22 -0
  197. package/dist/windows.d.ts.map +1 -0
  198. package/dist/windows.js +89 -0
  199. package/dist/wire.d.ts +32 -0
  200. package/dist/wire.d.ts.map +1 -1
  201. package/dist/wire.js +8 -0
  202. package/dist/words.d.ts +19 -0
  203. package/dist/words.d.ts.map +1 -0
  204. package/dist/words.js +150 -0
  205. package/package.json +10 -4
  206. package/dist/acquisition.d.ts +0 -14
  207. package/dist/acquisition.d.ts.map +0 -1
  208. package/dist/acquisition.js +0 -24
  209. package/dist/read-side.d.ts +0 -25
  210. package/dist/read-side.d.ts.map +0 -1
  211. 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,21 +294,36 @@ 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
- function changesOf(conversation, arriving) {
268
- return arriving.map(({ identity, text }) => ({ conversation, identity, text }));
269
- }
270
327
  function stateOf(conversation) {
271
328
  const facts = liveOf(conversation);
272
329
  if (facts.questions.length > 0)
@@ -281,6 +338,7 @@ export function createConversationStore(deps) {
281
338
  created: entry.created,
282
339
  finished: entry.finished,
283
340
  outcome: entry.outcome,
341
+ waiting: entry.waiting,
284
342
  state: stateOf(entry.conversation),
285
343
  started: lives.get(entry.conversation)?.turn?.started ?? null,
286
344
  }));
@@ -323,23 +381,30 @@ export function createConversationStore(deps) {
323
381
  const declaration = deps.agents();
324
382
  if (declaration === undefined)
325
383
  return;
326
- const held = await deps.documents(declaration);
327
- if ('pending' in held)
328
- return;
329
- const { documents } = held;
330
384
  const names = new Map();
331
385
  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);
386
+ let covered = null;
387
+ for (let after;;) {
388
+ const read = await deps.documents(declaration, after);
389
+ if ('pending' in read)
390
+ return;
391
+ const coverage = JSON.stringify(read.coverage);
392
+ if (covered !== null && coverage !== covered)
393
+ return readAgents();
394
+ covered = coverage;
395
+ for (const document of read.documents) {
396
+ const identity = document.fields?.[declaration.identity];
397
+ const name = document.fields?.[declaration.name];
398
+ if (typeof identity !== 'string' || typeof name !== 'string')
399
+ continue;
400
+ names.set(identity, name);
401
+ const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
402
+ if (ended !== undefined && ended !== null)
403
+ retired.add(identity);
404
+ }
405
+ if (read.next === undefined)
406
+ break;
407
+ after = read.next;
343
408
  }
344
409
  const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
345
410
  if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents]))
@@ -363,9 +428,34 @@ export function createConversationStore(deps) {
363
428
  return agentsRead;
364
429
  }
365
430
  function live(conversation, change) {
431
+ const owed = unsent.get(conversation);
432
+ for (const fact of FACTS.filter((one) => one in change))
433
+ owed?.delete(fact);
366
434
  setLive(conversation, { ...liveOf(conversation), ...change });
367
435
  emit(conversation);
368
436
  }
437
+ function openingEnded() {
438
+ const owed = [...unsent];
439
+ unsent.clear();
440
+ for (const [conversation, facts] of owed) {
441
+ const held = lives.get(conversation);
442
+ if (held === undefined || facts.size === 0)
443
+ continue;
444
+ setLive(conversation, {
445
+ turn: held.turn,
446
+ questions: facts.has('questions') ? QUIET.questions : held.questions,
447
+ process: facts.has('process') ? QUIET.process : held.process,
448
+ commands: facts.has('commands') ? QUIET.commands : held.commands,
449
+ options: facts.has('options') ? QUIET.options : held.options,
450
+ starting: facts.has('starting') ? QUIET.starting : held.starting,
451
+ link: facts.has('link') ? QUIET.link : held.link,
452
+ deliveryUnknown: facts.has('deliveryUnknown')
453
+ ? QUIET.deliveryUnknown
454
+ : held.deliveryUnknown,
455
+ });
456
+ emit(conversation);
457
+ }
458
+ }
369
459
  function relayed(conversation, turn, operation) {
370
460
  const held = liveOf(conversation).turn;
371
461
  const next = held?.turn === turn ? appliedTurn(held, operation) : startedTurn(turn, operation);
@@ -392,12 +482,6 @@ export function createConversationStore(deps) {
392
482
  }
393
483
  async function remove(leaving) {
394
484
  await deps.durably(async (local) => {
395
- const removed = [];
396
- for (const conversation of leaving) {
397
- for (const identity of await local.messageIdentities(conversation)) {
398
- removed.push({ conversation, identity, text: null });
399
- }
400
- }
401
485
  await local.dropConversations(leaving);
402
486
  const changed = [];
403
487
  for (const conversation of leaving) {
@@ -409,11 +493,12 @@ export function createConversationStore(deps) {
409
493
  partials.delete(conversation);
410
494
  drafts.delete(conversation);
411
495
  behind.delete(conversation);
496
+ unsent.delete(conversation);
412
497
  setLive(conversation, null);
413
- slide(conversation, null);
498
+ await slide(local, conversation, null);
414
499
  }
415
500
  if (changed.length > 0)
416
- deps.publish(conversationChangeSet(removed, changed));
501
+ deps.publish(conversationChangeSet(changed));
417
502
  });
418
503
  }
419
504
  function gap(conversation) {
@@ -438,11 +523,11 @@ export function createConversationStore(deps) {
438
523
  if (advances)
439
524
  reads.set(conversation, message.position);
440
525
  sealTurns(conversation, new Set([message.turn]));
441
- slide(conversation, [message]);
442
- deps.publish(conversationChangeSet(changesOf(conversation, [message]), [conversation]));
526
+ await slide(local, conversation, [message]);
527
+ deps.publish(conversationChangeSet([conversation]));
443
528
  });
444
529
  }
445
- async function readHistories(conversations) {
530
+ async function readHistories(conversations, ended) {
446
531
  const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
447
532
  let asking = conversations.map((conversation) => ({
448
533
  conversation,
@@ -468,10 +553,33 @@ export function createConversationStore(deps) {
468
553
  following.push({ conversation: page.conversation, after: page.next });
469
554
  }
470
555
  }
556
+ for (const { conversation } of asking) {
557
+ if (!following.some((next) => next.conversation === conversation))
558
+ ended(conversation);
559
+ }
471
560
  asking = following;
472
561
  }
473
562
  return null;
474
563
  }
564
+ function caughtUp(conversations) {
565
+ const ends = new Map();
566
+ for (const conversation of conversations) {
567
+ const held = new Promise((end) => ends.set(conversation, end));
568
+ catching.set(conversation, held);
569
+ void held.then(() => {
570
+ if (catching.get(conversation) === held)
571
+ catching.delete(conversation);
572
+ });
573
+ }
574
+ const ended = (conversation) => ends.get(conversation)?.();
575
+ const read = readHistories(conversations, ended);
576
+ const endAll = () => {
577
+ for (const conversation of conversations)
578
+ ended(conversation);
579
+ };
580
+ read.then(endAll, endAll);
581
+ return read;
582
+ }
475
583
  function keepPage(conversation, arriving, from, next, generation) {
476
584
  return deps.durably(async (local) => {
477
585
  if (!replicated.has(conversation))
@@ -490,9 +598,9 @@ export function createConversationStore(deps) {
490
598
  sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
491
599
  if (whole && (gaps.get(conversation) ?? 0) === generation)
492
600
  behind.delete(conversation);
493
- slide(conversation, arriving);
601
+ await slide(local, conversation, arriving);
494
602
  if (arriving.length > 0 || sealing || whole) {
495
- deps.publish(conversationChangeSet(changesOf(conversation, arriving), [conversation]));
603
+ deps.publish(conversationChangeSet([conversation]));
496
604
  }
497
605
  });
498
606
  }
@@ -501,11 +609,8 @@ export function createConversationStore(deps) {
501
609
  if (event.kind === 'gap') {
502
610
  if (replicated.has(conversation))
503
611
  gap(conversation);
504
- const held = lives.get(conversation);
505
- if (held === undefined)
506
- return;
507
- setLive(conversation, { ...QUIET, turn: held.turn });
508
- emit(conversation);
612
+ if (lives.has(conversation))
613
+ unsent.set(conversation, new Set(FACTS));
509
614
  return;
510
615
  }
511
616
  if (event.kind === 'turn') {
@@ -528,6 +633,18 @@ export function createConversationStore(deps) {
528
633
  live(conversation, { options: structuredClone(event.options) });
529
634
  return;
530
635
  }
636
+ if (event.kind === 'starting') {
637
+ live(conversation, { starting: event.step });
638
+ return;
639
+ }
640
+ if (event.kind === 'link') {
641
+ live(conversation, { link: { connected: event.connected } });
642
+ return;
643
+ }
644
+ if (event.kind === 'deliveries') {
645
+ live(conversation, { deliveryUnknown: [...event.unknown] });
646
+ return;
647
+ }
531
648
  const arriving = acceptedMessage(event.message);
532
649
  if (arriving === null)
533
650
  return;
@@ -567,7 +684,6 @@ export function createConversationStore(deps) {
567
684
  const blank = {
568
685
  conversation: from,
569
686
  text: '',
570
- files: [],
571
687
  revision: chatDraftRevision(),
572
688
  };
573
689
  const { moves: _answered, ...unsent } = held ?? blank;
@@ -577,7 +693,7 @@ export function createConversationStore(deps) {
577
693
  drafts.set(from, left);
578
694
  for (const one of taken)
579
695
  drafts.set(to, one);
580
- deps.publish(conversationChangeSet([], [from, to]));
696
+ deps.publish(conversationChangeSet([from, to]));
581
697
  });
582
698
  }
583
699
  function withDraft(conversation, next) {
@@ -586,7 +702,7 @@ export function createConversationStore(deps) {
586
702
  if (following === undefined)
587
703
  return;
588
704
  await kept(local, conversation, following);
589
- deps.publish(conversationChangeSet([], [conversation]));
705
+ deps.publish(conversationChangeSet([conversation]));
590
706
  });
591
707
  }
592
708
  async function submitted(conversation, operation, draft) {
@@ -598,17 +714,12 @@ export function createConversationStore(deps) {
598
714
  if (submission !== undefined)
599
715
  return undefined;
600
716
  const said = draft ?? current;
601
- submission = {
602
- operation,
603
- text: said?.text ?? '',
604
- files: [...(said?.files ?? [])],
605
- turn: null,
606
- };
717
+ submission = { operation, text: said?.text ?? '', turn: null };
607
718
  return {
608
719
  conversation,
609
720
  ...(draft === undefined || current === undefined
610
- ? { text: '', files: [], revision: chatDraftRevision() }
611
- : { text: current.text, files: current.files, revision: current.revision }),
721
+ ? { text: '', revision: chatDraftRevision() }
722
+ : { text: current.text, revision: current.revision }),
612
723
  submitted: [...(current?.submitted ?? []), submission],
613
724
  };
614
725
  });
@@ -626,10 +737,8 @@ export function createConversationStore(deps) {
626
737
  const dispatch = {};
627
738
  sending.set(submission.operation, dispatch);
628
739
  try {
629
- const files = await Promise.all(submission.files.map(async (file) => ({ ...(await declaredFile(file)), blob: file.blob })));
630
740
  const result = await invoke('conversation.send', conversation, {
631
741
  text: submission.text,
632
- files,
633
742
  operation_id: submission.operation,
634
743
  });
635
744
  if (result.kind === 'revoked-authorization')
@@ -667,8 +776,12 @@ export function createConversationStore(deps) {
667
776
  return entries();
668
777
  },
669
778
  async read(conversation, options = {}) {
670
- const limit = limitOf(options);
671
- return deps.durably(async (local) => structuredClone(viewOf(conversation, await local.messageWindow(conversation, limit))));
779
+ const asked = askedOf(options);
780
+ return deps.durably(async (local) => {
781
+ const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
782
+ const total = asked.total ? await local.messageCount(conversation) : null;
783
+ return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total)));
784
+ });
672
785
  },
673
786
  async create(input) {
674
787
  const result = await deps.call('conversation.create', input);
@@ -693,10 +806,19 @@ export function createConversationStore(deps) {
693
806
  if (draft !== undefined) {
694
807
  await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
695
808
  ? undefined
696
- : returned(current ?? { conversation, text: '', files: [], revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
809
+ : returned(current ?? { conversation, text: '', revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
697
810
  }
698
811
  return OFFLINE;
699
812
  },
813
+ async attach(room, file) {
814
+ if (!deps.online())
815
+ return OFFLINE;
816
+ return deps.call('conversation.attach', {
817
+ room,
818
+ ...(await declaredFile(file)),
819
+ blob: file.blob,
820
+ });
821
+ },
700
822
  answer(conversation, answer) {
701
823
  return invoke('conversation.answer', conversation, { answer });
702
824
  },
@@ -727,21 +849,22 @@ export function createConversationStore(deps) {
727
849
  return withDraft(conversation, (current) => ({
728
850
  conversation,
729
851
  text: draft.text,
730
- files: [...draft.files],
731
852
  revision,
732
853
  ...(current === undefined ? {} : pending(current)),
733
854
  }));
734
855
  },
856
+ prepend(conversation, draft) {
857
+ return withDraft(conversation, (current) => ({
858
+ conversation,
859
+ ...(current === undefined ? { text: draft.text } : prepended(current, draft)),
860
+ revision: chatDraftRevision(),
861
+ ...(current === undefined ? {} : pending(current)),
862
+ }));
863
+ },
735
864
  discard(conversation) {
736
865
  return withDraft(conversation, (current) => current === undefined
737
866
  ? undefined
738
- : {
739
- conversation,
740
- text: '',
741
- files: [],
742
- revision: chatDraftRevision(),
743
- ...pending(current),
744
- });
867
+ : { conversation, text: '', revision: chatDraftRevision(), ...pending(current) });
745
868
  },
746
869
  };
747
870
  return {
@@ -781,12 +904,20 @@ export function createConversationStore(deps) {
781
904
  await remove(leaving);
782
905
  },
783
906
  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);
907
+ const owed = (conversation) => behind.has(conversation) && replicated.has(conversation);
908
+ const read = async (conversations) => {
909
+ if (conversations.length === 0 || deps.closed())
910
+ return;
911
+ const failed = await caughtUp(conversations);
787
912
  if (failed?.kind === 'transport-uncertainty')
788
913
  progress.reachable = false;
789
- }
914
+ };
915
+ const asking = [...behind].filter(owed).sort();
916
+ const awaited = asking.filter((conversation) => catching.has(conversation));
917
+ await Promise.all([
918
+ read(asking.filter((conversation) => !catching.has(conversation))),
919
+ Promise.all(awaited.map((conversation) => catching.get(conversation))).then(() => read(awaited.filter((conversation) => owed(conversation) && !catching.has(conversation)))),
920
+ ]);
790
921
  if (behind.size > 0)
791
922
  progress.complete = false;
792
923
  },
@@ -828,27 +959,38 @@ export function createConversationStore(deps) {
828
959
  reopened() {
829
960
  sending.clear();
830
961
  },
962
+ lost() {
963
+ catching.clear();
964
+ },
831
965
  watch(conversation, options, deliver) {
832
- const limit = limitOf(options);
833
- const key = JSON.stringify([conversation, limit ?? null]);
966
+ const asked = askedOf(options);
967
+ const key = JSON.stringify([conversation, asked.limit, asked.after ?? null, asked.total]);
834
968
  const view = views.get(key) ?? {
835
969
  conversation,
836
- limit,
970
+ asked,
837
971
  watchers: new Set(),
838
972
  window: null,
973
+ more: false,
974
+ total: null,
839
975
  };
840
976
  views.set(key, view);
841
977
  const watcher = { notify: deliver, ready: false };
842
978
  view.watchers.add(watcher);
843
979
  void deps
844
980
  .durably(async (local) => {
845
- view.window ??= await local.messageWindow(conversation, limit);
981
+ if (view.window !== null)
982
+ return;
983
+ const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
984
+ view.window = messages;
985
+ view.more = more;
986
+ view.total = asked.total ? await local.messageCount(conversation) : null;
846
987
  })
847
988
  .then(() => {
848
989
  if (deps.closed() || !view.watchers.has(watcher))
849
990
  return;
850
991
  watcher.ready = true;
851
- watcher.notify(viewOf(conversation, view.window ?? []));
992
+ const window = view.window ?? [];
993
+ watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
852
994
  })
853
995
  .catch(() => { });
854
996
  return () => {
@@ -900,6 +1042,7 @@ export function createConversationStore(deps) {
900
1042
  creations.delete(entry.conversation);
901
1043
  }
902
1044
  listKnown = true;
1045
+ openingEnded();
903
1046
  }
904
1047
  else if (event.kind === 'listed') {
905
1048
  listing.set(event.entry.conversation, { ...event.entry });
@@ -926,6 +1069,7 @@ export function createConversationStore(deps) {
926
1069
  close() {
927
1070
  views.clear();
928
1071
  lives.clear();
1072
+ unsent.clear();
929
1073
  listWatchers.clear();
930
1074
  agentWatchers.clear();
931
1075
  },
@@ -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"}