@agentshouse/mdruntime 0.1.0-c1db74d14e5fa → 0.1.0-c1ffd2bc60355

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 (220) hide show
  1. package/AGENTS.md +268 -251
  2. package/README.md +9 -8
  3. package/dist/adapter.d.ts +76 -40
  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/binary.d.ts +0 -5
  9. package/dist/binary.d.ts.map +1 -1
  10. package/dist/binary.js +1 -1
  11. package/dist/bootstrap.d.ts +26 -7
  12. package/dist/bootstrap.d.ts.map +1 -1
  13. package/dist/bootstrap.js +148 -78
  14. package/dist/chain.d.ts +11 -0
  15. package/dist/chain.d.ts.map +1 -0
  16. package/dist/chain.js +64 -0
  17. package/dist/change-set.d.ts +9 -16
  18. package/dist/change-set.d.ts.map +1 -1
  19. package/dist/change-set.js +11 -88
  20. package/dist/change.d.ts +31 -1
  21. package/dist/change.d.ts.map +1 -1
  22. package/dist/change.js +10 -1
  23. package/dist/chat/earlier.d.ts +10 -0
  24. package/dist/chat/earlier.d.ts.map +1 -0
  25. package/dist/chat/earlier.js +54 -0
  26. package/dist/chat/house.d.ts +25 -0
  27. package/dist/chat/house.d.ts.map +1 -0
  28. package/dist/chat/house.js +38 -0
  29. package/dist/chat/input.d.ts +3 -1
  30. package/dist/chat/input.d.ts.map +1 -1
  31. package/dist/chat/input.js +11 -18
  32. package/dist/chat/kit.d.ts +2 -1
  33. package/dist/chat/kit.d.ts.map +1 -1
  34. package/dist/chat/kit.js +3 -3
  35. package/dist/chat/list.d.ts +3 -1
  36. package/dist/chat/list.d.ts.map +1 -1
  37. package/dist/chat/list.js +47 -6
  38. package/dist/chat/markdown.d.ts +2 -2
  39. package/dist/chat/markdown.d.ts.map +1 -1
  40. package/dist/chat/markdown.js +80 -39
  41. package/dist/chat/message.d.ts +1 -0
  42. package/dist/chat/message.d.ts.map +1 -1
  43. package/dist/chat/message.js +73 -11
  44. package/dist/chat/messages.d.ts +15 -1
  45. package/dist/chat/messages.d.ts.map +1 -1
  46. package/dist/chat/messages.js +82 -23
  47. package/dist/chat/name.d.ts +1 -0
  48. package/dist/chat/name.d.ts.map +1 -1
  49. package/dist/chat/name.js +1 -1
  50. package/dist/chat/opened.d.ts +1 -0
  51. package/dist/chat/opened.d.ts.map +1 -1
  52. package/dist/chat/opened.js +2 -0
  53. package/dist/chat/overlay.d.ts +1 -0
  54. package/dist/chat/overlay.d.ts.map +1 -1
  55. package/dist/chat/overlay.js +11 -5
  56. package/dist/chat/parts.d.ts +1 -0
  57. package/dist/chat/parts.d.ts.map +1 -1
  58. package/dist/chat/parts.js +3 -2
  59. package/dist/chat/pieces.d.ts +62 -0
  60. package/dist/chat/pieces.d.ts.map +1 -0
  61. package/dist/chat/pieces.js +1110 -0
  62. package/dist/chat/provider.d.ts +9 -2
  63. package/dist/chat/provider.d.ts.map +1 -1
  64. package/dist/chat/provider.js +121 -106
  65. package/dist/chat/question.d.ts +1 -1
  66. package/dist/chat/question.d.ts.map +1 -1
  67. package/dist/chat/question.js +7 -18
  68. package/dist/chat/ready.d.ts +5 -1
  69. package/dist/chat/ready.d.ts.map +1 -1
  70. package/dist/chat/ready.js +7 -5
  71. package/dist/chat/room-file.d.ts +8 -0
  72. package/dist/chat/room-file.d.ts.map +1 -0
  73. package/dist/chat/room-file.js +72 -0
  74. package/dist/chat/steady.d.ts +3 -0
  75. package/dist/chat/steady.d.ts.map +1 -0
  76. package/dist/chat/steady.js +26 -0
  77. package/dist/chat/style.d.ts.map +1 -1
  78. package/dist/chat/style.js +6 -1
  79. package/dist/chat.d.ts +3 -2
  80. package/dist/chat.d.ts.map +1 -1
  81. package/dist/chat.js +2 -2
  82. package/dist/client.d.ts +15 -6
  83. package/dist/client.d.ts.map +1 -1
  84. package/dist/client.js +47 -8
  85. package/dist/collection.d.ts +6 -19
  86. package/dist/collection.d.ts.map +1 -1
  87. package/dist/collection.js +30 -287
  88. package/dist/compile.d.ts +16 -0
  89. package/dist/compile.d.ts.map +1 -0
  90. package/dist/compile.js +721 -0
  91. package/dist/connection.d.ts +25 -12
  92. package/dist/connection.d.ts.map +1 -1
  93. package/dist/connection.js +2615 -946
  94. package/dist/conversation.d.ts +18 -3
  95. package/dist/conversation.d.ts.map +1 -1
  96. package/dist/conversation.js +341 -107
  97. package/dist/digest.d.ts +7 -0
  98. package/dist/digest.d.ts.map +1 -0
  99. package/dist/digest.js +113 -0
  100. package/dist/durability.d.ts +3 -5
  101. package/dist/durability.d.ts.map +1 -1
  102. package/dist/durability.js +5 -0
  103. package/dist/entry.d.ts +3 -1
  104. package/dist/entry.d.ts.map +1 -1
  105. package/dist/entry.js +18 -23
  106. package/dist/file.d.ts +2 -10
  107. package/dist/file.d.ts.map +1 -1
  108. package/dist/file.js +3 -120
  109. package/dist/fulltext.d.ts +27 -0
  110. package/dist/fulltext.d.ts.map +1 -0
  111. package/dist/fulltext.js +368 -0
  112. package/dist/host.d.ts +10 -8
  113. package/dist/host.d.ts.map +1 -1
  114. package/dist/host.js +95 -18
  115. package/dist/identity.d.ts +0 -1
  116. package/dist/identity.d.ts.map +1 -1
  117. package/dist/identity.js +0 -3
  118. package/dist/index.d.ts +15 -11
  119. package/dist/index.d.ts.map +1 -1
  120. package/dist/index.js +5 -1
  121. package/dist/live.d.ts +22 -0
  122. package/dist/live.d.ts.map +1 -0
  123. package/dist/live.js +123 -0
  124. package/dist/memory.d.ts +8 -3
  125. package/dist/memory.d.ts.map +1 -1
  126. package/dist/memory.js +43 -25
  127. package/dist/messages.d.ts +28 -0
  128. package/dist/messages.d.ts.map +1 -0
  129. package/dist/messages.js +90 -0
  130. package/dist/model.d.ts +3 -2
  131. package/dist/model.d.ts.map +1 -1
  132. package/dist/model.js +1 -1
  133. package/dist/name.d.ts +4 -0
  134. package/dist/name.d.ts.map +1 -0
  135. package/dist/name.js +122 -0
  136. package/dist/observation.d.ts +14 -0
  137. package/dist/observation.d.ts.map +1 -1
  138. package/dist/observation.js +93 -3
  139. package/dist/opened.d.ts +22 -0
  140. package/dist/opened.d.ts.map +1 -0
  141. package/dist/opened.js +107 -0
  142. package/dist/outcome.d.ts +34 -4
  143. package/dist/outcome.d.ts.map +1 -1
  144. package/dist/outcome.js +25 -0
  145. package/dist/overlay.d.ts +3 -5
  146. package/dist/overlay.d.ts.map +1 -1
  147. package/dist/overlay.js +95 -156
  148. package/dist/query.d.ts +33 -2
  149. package/dist/query.d.ts.map +1 -1
  150. package/dist/query.js +48 -7
  151. package/dist/reach.d.ts +6 -5
  152. package/dist/reach.d.ts.map +1 -1
  153. package/dist/reach.js +170 -62
  154. package/dist/react.d.ts +3 -3
  155. package/dist/react.d.ts.map +1 -1
  156. package/dist/react.js +7 -17
  157. package/dist/readiness.d.ts +20 -9
  158. package/dist/readiness.d.ts.map +1 -1
  159. package/dist/readiness.js +6 -6
  160. package/dist/reads.d.ts +11 -0
  161. package/dist/reads.d.ts.map +1 -0
  162. package/dist/reads.js +100 -0
  163. package/dist/recovery.d.ts +2 -2
  164. package/dist/recovery.d.ts.map +1 -1
  165. package/dist/recovery.js +8 -7
  166. package/dist/refresh.d.ts +16 -15
  167. package/dist/refresh.d.ts.map +1 -1
  168. package/dist/refresh.js +112 -45
  169. package/dist/replica.d.ts +57 -33
  170. package/dist/replica.d.ts.map +1 -1
  171. package/dist/replica.js +295 -177
  172. package/dist/room-draft.d.ts +17 -2
  173. package/dist/room-draft.d.ts.map +1 -1
  174. package/dist/room-draft.js +65 -25
  175. package/dist/rows.d.ts +29 -29
  176. package/dist/rows.d.ts.map +1 -1
  177. package/dist/rows.js +113 -62
  178. package/dist/save.d.ts +51 -14
  179. package/dist/save.d.ts.map +1 -1
  180. package/dist/save.js +518 -180
  181. package/dist/search.d.ts +32 -43
  182. package/dist/search.d.ts.map +1 -1
  183. package/dist/search.js +37 -168
  184. package/dist/seed.d.ts +16 -0
  185. package/dist/seed.d.ts.map +1 -0
  186. package/dist/seed.js +75 -0
  187. package/dist/shared.d.ts +8 -3
  188. package/dist/shared.d.ts.map +1 -1
  189. package/dist/shared.js +129 -66
  190. package/dist/store.d.ts +99 -88
  191. package/dist/store.d.ts.map +1 -1
  192. package/dist/store.js +335 -641
  193. package/dist/stored.d.ts +145 -0
  194. package/dist/stored.d.ts.map +1 -0
  195. package/dist/stored.js +567 -0
  196. package/dist/tables.d.ts +354 -0
  197. package/dist/tables.d.ts.map +1 -0
  198. package/dist/tables.js +624 -0
  199. package/dist/turn.d.ts +5 -3
  200. package/dist/turn.d.ts.map +1 -1
  201. package/dist/turn.js +51 -21
  202. package/dist/validation.d.ts +30 -0
  203. package/dist/validation.d.ts.map +1 -0
  204. package/dist/validation.js +162 -0
  205. package/dist/windows.d.ts +22 -0
  206. package/dist/windows.d.ts.map +1 -0
  207. package/dist/windows.js +89 -0
  208. package/dist/wire.d.ts +163 -33
  209. package/dist/wire.d.ts.map +1 -1
  210. package/dist/wire.js +54 -32
  211. package/dist/words.d.ts +19 -0
  212. package/dist/words.d.ts.map +1 -0
  213. package/dist/words.js +150 -0
  214. package/package.json +11 -5
  215. package/dist/acquisition.d.ts +0 -14
  216. package/dist/acquisition.d.ts.map +0 -1
  217. package/dist/acquisition.js +0 -24
  218. package/dist/read-side.d.ts +0 -25
  219. package/dist/read-side.d.ts.map +0 -1
  220. package/dist/read-side.js +0 -639
@@ -1,15 +1,29 @@
1
1
  import { conversationChangeSet } from './change-set.js';
2
2
  import { runtimeError } from './failure.js';
3
+ import { refusedForSpace } from './durability.js';
3
4
  import { declaredFile } from './file.js';
4
5
  import { changeBatchIdentity, chatDraftRevision } from './identity.js';
5
6
  import { OFFLINE } from './outcome.js';
7
+ import { capped, cursorOf, decodedCursor, FROM_START, messageOrder, precedes } from './windows.js';
6
8
  const QUIET = {
7
9
  turn: null,
8
10
  questions: [],
9
11
  process: null,
10
12
  commands: [],
11
13
  options: [],
14
+ starting: null,
15
+ link: null,
16
+ deliveryUnknown: [],
12
17
  };
18
+ const FACTS = [
19
+ 'questions',
20
+ 'process',
21
+ 'commands',
22
+ 'options',
23
+ 'starting',
24
+ 'link',
25
+ 'deliveryUnknown',
26
+ ];
13
27
  export function unheldView(conversation) {
14
28
  return {
15
29
  conversation,
@@ -19,10 +33,26 @@ export function unheldView(conversation) {
19
33
  process: null,
20
34
  commands: [],
21
35
  options: [],
36
+ starting: null,
37
+ link: null,
38
+ deliveryUnknown: [],
22
39
  chatDraft: null,
23
40
  };
24
41
  }
25
42
  export const SESSION_STARTED = 'Session started';
43
+ const NEW_CHAT = 'new:';
44
+ export const HISTORY_CONVERSATIONS = 64;
45
+ export function newChat(agent) {
46
+ return `${NEW_CHAT}${agent}`;
47
+ }
48
+ function creationOf(input) {
49
+ if (typeof input !== 'object' || input === null)
50
+ return null;
51
+ const { agent_id: agent, operation_id: operation, replaces, } = input;
52
+ return typeof agent === 'string' && typeof operation === 'string' && replaces === undefined
53
+ ? { agent, operation }
54
+ : null;
55
+ }
26
56
  function startedTurn(turn, operation) {
27
57
  if (operation.op === 'begin')
28
58
  return { turn, started: operation.at, parts: [], context: null };
@@ -72,19 +102,25 @@ function appliedTurn(held, operation) {
72
102
  parts: held.parts.map((kept, at) => (at === operation.index ? changed : kept)),
73
103
  };
74
104
  }
75
- function ordered(left, right) {
76
- if (left.position !== right.position)
77
- return left.position < right.position ? -1 : 1;
78
- if (left.identity !== right.identity)
79
- return left.identity < right.identity ? -1 : 1;
80
- return 0;
81
- }
82
105
  function windowOf(held, arriving, limit) {
83
106
  const byIdentity = new Map(held.map((message) => [message.identity, message]));
84
107
  for (const message of arriving)
85
108
  byIdentity.set(message.identity, message);
86
- const next = [...byIdentity.values()].sort(ordered);
87
- return limit === undefined ? next : next.slice(Math.max(0, next.length - limit));
109
+ const next = [...byIdentity.values()].sort(messageOrder);
110
+ const start = Math.max(0, next.length - limit);
111
+ return { messages: next.slice(start), dropped: start > 0 };
112
+ }
113
+ function frameOf(asked, messages, more, total) {
114
+ const oldest = messages[0];
115
+ const next = !more
116
+ ? undefined
117
+ : oldest === undefined
118
+ ? (asked.after ?? FROM_START)
119
+ : cursorOf([oldest.position, oldest.identity]);
120
+ return {
121
+ ...(next === undefined ? {} : { next }),
122
+ ...(total === null ? {} : { total }),
123
+ };
88
124
  }
89
125
  function newestOf(arriving, from) {
90
126
  return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
@@ -146,40 +182,64 @@ function historyPages(result) {
146
182
  }
147
183
  return pages;
148
184
  }
149
- function turnOf(result) {
185
+ function answered(result, field) {
150
186
  if (result.kind !== 'action')
151
187
  return null;
152
188
  const answer = result.result;
153
189
  if (typeof answer !== 'object' || answer === null)
154
190
  return null;
155
- const record = answer;
156
- return typeof record.turn === 'string' ? record.turn : null;
191
+ const value = answer[field];
192
+ return typeof value === 'string' ? value : null;
157
193
  }
158
- function limitOf(options) {
159
- const { limit } = options;
160
- if (limit === undefined)
161
- return undefined;
162
- if (!Number.isInteger(limit) || limit < 0) {
194
+ function beforeOf(after) {
195
+ if (after === undefined)
196
+ return null;
197
+ const decoded = typeof after === 'string' ? decodedCursor(after) : null;
198
+ if (decoded?.length === 0)
199
+ return null;
200
+ const [position, identity] = decoded ?? [];
201
+ if (decoded?.length !== 2 || typeof position !== 'string' || typeof identity !== 'string') {
202
+ throw runtimeError('invalid-after', `conversation after must be the next an earlier view returned, got ${String(after)}`);
203
+ }
204
+ return [position, identity];
205
+ }
206
+ function askedOf(options) {
207
+ const { limit, after, total } = options;
208
+ if (limit !== undefined && (!Number.isInteger(limit) || limit < 0)) {
163
209
  throw runtimeError('invalid-limit', `conversation limit must be a non-negative integer, got ${String(limit)}`);
164
210
  }
165
- return limit;
211
+ return { limit: capped(limit), after, before: beforeOf(after), total: total === true };
166
212
  }
167
213
  const EXPIRED = { kind: 'rejected', code: 'expired', fields: {}, retryable: false };
214
+ const DURABLE_WRITE = {
215
+ kind: 'rejected',
216
+ code: 'durable-write',
217
+ fields: {},
218
+ retryable: true,
219
+ };
168
220
  function unwritten(draft) {
169
- return draft.text === '' && draft.files.length === 0;
221
+ return draft.text === '';
170
222
  }
171
223
  function pending(draft) {
172
- return draft.submitted === undefined ? {} : { submitted: draft.submitted };
224
+ return {
225
+ ...(draft.submitted === undefined ? {} : { submitted: draft.submitted }),
226
+ ...(draft.moves === undefined ? {} : { moves: draft.moves }),
227
+ };
228
+ }
229
+ function empty(draft) {
230
+ return unwritten(draft) && draft.submitted === undefined && draft.moves === undefined;
173
231
  }
174
232
  function withSubmitted(draft, submitted) {
175
233
  const { submitted: _held, ...unsent } = draft;
176
234
  return submitted.length === 0 ? unsent : { ...unsent, submitted };
177
235
  }
236
+ function prepended(draft, said) {
237
+ return { text: [said.text, draft.text].filter((one) => one !== '').join('\n') };
238
+ }
178
239
  function returned(draft, submission, others) {
179
240
  return withSubmitted({
180
241
  conversation: draft.conversation,
181
- text: [submission.text, draft.text].filter((one) => one !== '').join('\n'),
182
- files: [...submission.files, ...draft.files],
242
+ ...prepended(draft, submission),
183
243
  revision: chatDraftRevision(),
184
244
  }, others);
185
245
  }
@@ -190,10 +250,13 @@ export function createConversationStore(deps) {
190
250
  const drafts = new Map();
191
251
  const sending = new Map();
192
252
  const behind = new Set();
253
+ const catching = new Map();
193
254
  const gaps = new Map();
194
255
  const lives = new Map();
256
+ const unsent = new Map();
195
257
  const views = new Map();
196
258
  const listing = new Map();
259
+ const creations = new Set();
197
260
  const listWatchers = new Set();
198
261
  let listKnown = false;
199
262
  const agentWatchers = new Set();
@@ -205,7 +268,7 @@ export function createConversationStore(deps) {
205
268
  function liveOf(conversation) {
206
269
  return lives.get(conversation) ?? QUIET;
207
270
  }
208
- function viewOf(conversation, held) {
271
+ function viewOf(conversation, held, frame) {
209
272
  const draft = drafts.get(conversation);
210
273
  const facts = liveOf(conversation);
211
274
  return {
@@ -216,14 +279,18 @@ export function createConversationStore(deps) {
216
279
  text,
217
280
  message,
218
281
  })),
282
+ ...frame,
219
283
  turn: structuredClone(facts.turn),
220
284
  questions: structuredClone(facts.questions),
221
285
  process: facts.process === null ? null : { ...facts.process },
222
286
  commands: structuredClone(facts.commands),
223
287
  options: structuredClone(facts.options),
288
+ starting: facts.starting,
289
+ link: facts.link === null ? null : { ...facts.link },
290
+ deliveryUnknown: [...facts.deliveryUnknown],
224
291
  chatDraft: draft === undefined || unwritten(draft)
225
292
  ? null
226
- : { text: draft.text, files: [...draft.files], revision: draft.revision },
293
+ : { text: draft.text, revision: draft.revision },
227
294
  };
228
295
  }
229
296
  function emit(conversation) {
@@ -235,21 +302,36 @@ export function createConversationStore(deps) {
235
302
  const ready = [...view.watchers].filter((watcher) => watcher.ready);
236
303
  if (ready.length === 0)
237
304
  continue;
238
- const value = viewOf(conversation, view.window);
305
+ const value = viewOf(conversation, view.window, sharedFrame(view, view.window));
239
306
  for (const watcher of ready)
240
307
  watcher.notify(value);
241
308
  }
242
309
  }
243
- function slide(conversation, arriving) {
310
+ function sharedFrame(view, window) {
311
+ return frameOf(view.asked, window, view.more, view.total);
312
+ }
313
+ async function slide(local, conversation, arriving) {
314
+ let count;
244
315
  for (const view of views.values()) {
245
316
  if (view.conversation !== conversation || view.window === null)
246
317
  continue;
247
- view.window = arriving === null ? [] : windowOf(view.window, arriving, view.limit);
318
+ if (arriving === null) {
319
+ view.window = [];
320
+ view.more = false;
321
+ if (view.total !== null)
322
+ view.total = 0;
323
+ continue;
324
+ }
325
+ const joining = arriving.filter((message) => precedes(message, view.asked.before));
326
+ const next = windowOf(view.window, joining, view.asked.limit);
327
+ view.window = next.messages;
328
+ view.more ||= next.dropped;
329
+ if (view.total !== null && arriving.length > 0) {
330
+ count ??= await local.messageCount(conversation);
331
+ view.total = count;
332
+ }
248
333
  }
249
334
  }
250
- function changesOf(conversation, arriving) {
251
- return arriving.map(({ identity, text }) => ({ conversation, identity, text }));
252
- }
253
335
  function stateOf(conversation) {
254
336
  const facts = liveOf(conversation);
255
337
  if (facts.questions.length > 0)
@@ -264,6 +346,7 @@ export function createConversationStore(deps) {
264
346
  created: entry.created,
265
347
  finished: entry.finished,
266
348
  outcome: entry.outcome,
349
+ waiting: entry.waiting,
267
350
  state: stateOf(entry.conversation),
268
351
  started: lives.get(entry.conversation)?.turn?.started ?? null,
269
352
  }));
@@ -306,23 +389,30 @@ export function createConversationStore(deps) {
306
389
  const declaration = deps.agents();
307
390
  if (declaration === undefined)
308
391
  return;
309
- const held = await deps.documents(declaration);
310
- if ('pending' in held)
311
- return;
312
- const { documents } = held;
313
392
  const names = new Map();
314
393
  const retired = new Set();
315
- for (const document of documents) {
316
- if (document.type !== declaration.type)
317
- continue;
318
- const identity = document.fields?.[declaration.identity];
319
- const name = document.fields?.[declaration.name];
320
- if (typeof identity !== 'string' || typeof name !== 'string')
321
- continue;
322
- names.set(identity, name);
323
- const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
324
- if (ended !== undefined && ended !== null)
325
- retired.add(identity);
394
+ let covered = null;
395
+ for (let after;;) {
396
+ const read = await deps.documents(declaration, after);
397
+ if ('pending' in read)
398
+ return;
399
+ const coverage = JSON.stringify(read.coverage);
400
+ if (covered !== null && coverage !== covered)
401
+ return readAgents();
402
+ covered = coverage;
403
+ for (const document of read.documents) {
404
+ const identity = document.fields?.[declaration.identity];
405
+ const name = document.fields?.[declaration.name];
406
+ if (typeof identity !== 'string' || typeof name !== 'string')
407
+ continue;
408
+ names.set(identity, name);
409
+ const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
410
+ if (ended !== undefined && ended !== null)
411
+ retired.add(identity);
412
+ }
413
+ if (read.next === undefined)
414
+ break;
415
+ after = read.next;
326
416
  }
327
417
  const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
328
418
  if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents]))
@@ -332,6 +422,12 @@ export function createConversationStore(deps) {
332
422
  if (named)
333
423
  emitList();
334
424
  emitAgents();
425
+ const ended = [...drafts.keys()].filter(retiredChat);
426
+ if (ended.length > 0)
427
+ await remove(ended);
428
+ }
429
+ function retiredChat(conversation) {
430
+ return (conversation.startsWith(NEW_CHAT) && retiredAgents.has(conversation.slice(NEW_CHAT.length)));
335
431
  }
336
432
  function agentsReady() {
337
433
  agentsRead ??= readAgents().catch(() => {
@@ -340,9 +436,34 @@ export function createConversationStore(deps) {
340
436
  return agentsRead;
341
437
  }
342
438
  function live(conversation, change) {
439
+ const owed = unsent.get(conversation);
440
+ for (const fact of FACTS.filter((one) => one in change))
441
+ owed?.delete(fact);
343
442
  setLive(conversation, { ...liveOf(conversation), ...change });
344
443
  emit(conversation);
345
444
  }
445
+ function openingEnded() {
446
+ const owed = [...unsent];
447
+ unsent.clear();
448
+ for (const [conversation, facts] of owed) {
449
+ const held = lives.get(conversation);
450
+ if (held === undefined || facts.size === 0)
451
+ continue;
452
+ setLive(conversation, {
453
+ turn: held.turn,
454
+ questions: facts.has('questions') ? QUIET.questions : held.questions,
455
+ process: facts.has('process') ? QUIET.process : held.process,
456
+ commands: facts.has('commands') ? QUIET.commands : held.commands,
457
+ options: facts.has('options') ? QUIET.options : held.options,
458
+ starting: facts.has('starting') ? QUIET.starting : held.starting,
459
+ link: facts.has('link') ? QUIET.link : held.link,
460
+ deliveryUnknown: facts.has('deliveryUnknown')
461
+ ? QUIET.deliveryUnknown
462
+ : held.deliveryUnknown,
463
+ });
464
+ emit(conversation);
465
+ }
466
+ }
346
467
  function relayed(conversation, turn, operation) {
347
468
  const held = liveOf(conversation).turn;
348
469
  const next = held?.turn === turn ? appliedTurn(held, operation) : startedTurn(turn, operation);
@@ -369,12 +490,6 @@ export function createConversationStore(deps) {
369
490
  }
370
491
  async function remove(leaving) {
371
492
  await deps.durably(async (local) => {
372
- const removed = [];
373
- for (const conversation of leaving) {
374
- for (const identity of await local.messageIdentities(conversation)) {
375
- removed.push({ conversation, identity, text: null });
376
- }
377
- }
378
493
  await local.dropConversations(leaving);
379
494
  const changed = [];
380
495
  for (const conversation of leaving) {
@@ -386,11 +501,12 @@ export function createConversationStore(deps) {
386
501
  partials.delete(conversation);
387
502
  drafts.delete(conversation);
388
503
  behind.delete(conversation);
504
+ unsent.delete(conversation);
389
505
  setLive(conversation, null);
390
- slide(conversation, null);
506
+ await slide(local, conversation, null);
391
507
  }
392
508
  if (changed.length > 0)
393
- deps.publish(conversationChangeSet(removed, changed));
509
+ deps.publish(conversationChangeSet(changed));
394
510
  });
395
511
  }
396
512
  function gap(conversation) {
@@ -415,17 +531,18 @@ export function createConversationStore(deps) {
415
531
  if (advances)
416
532
  reads.set(conversation, message.position);
417
533
  sealTurns(conversation, new Set([message.turn]));
418
- slide(conversation, [message]);
419
- deps.publish(conversationChangeSet(changesOf(conversation, [message]), [conversation]));
534
+ await slide(local, conversation, [message]);
535
+ deps.publish(conversationChangeSet([conversation]));
420
536
  });
421
537
  }
422
- async function readHistories(conversations) {
538
+ async function readHistories(conversations, ended) {
423
539
  const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
424
- let asking = conversations.map((conversation) => ({
540
+ let waiting = conversations.map((conversation) => ({
425
541
  conversation,
426
542
  after: reads.get(conversation) ?? undefined,
427
543
  }));
428
- while (asking.length > 0) {
544
+ while (waiting.length > 0) {
545
+ const asking = waiting.slice(0, HISTORY_CONVERSATIONS);
429
546
  const result = await deps.call('conversation.history', {
430
547
  conversations: asking.map(({ conversation, after }) => ({
431
548
  conversation,
@@ -445,10 +562,33 @@ export function createConversationStore(deps) {
445
562
  following.push({ conversation: page.conversation, after: page.next });
446
563
  }
447
564
  }
448
- asking = following;
565
+ for (const { conversation } of asking) {
566
+ if (!following.some((next) => next.conversation === conversation))
567
+ ended(conversation);
568
+ }
569
+ waiting = [...waiting.slice(HISTORY_CONVERSATIONS), ...following];
449
570
  }
450
571
  return null;
451
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
+ }
452
592
  function keepPage(conversation, arriving, from, next, generation) {
453
593
  return deps.durably(async (local) => {
454
594
  if (!replicated.has(conversation))
@@ -467,9 +607,9 @@ export function createConversationStore(deps) {
467
607
  sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
468
608
  if (whole && (gaps.get(conversation) ?? 0) === generation)
469
609
  behind.delete(conversation);
470
- slide(conversation, arriving);
610
+ await slide(local, conversation, arriving);
471
611
  if (arriving.length > 0 || sealing || whole) {
472
- deps.publish(conversationChangeSet(changesOf(conversation, arriving), [conversation]));
612
+ deps.publish(conversationChangeSet([conversation]));
473
613
  }
474
614
  });
475
615
  }
@@ -478,11 +618,8 @@ export function createConversationStore(deps) {
478
618
  if (event.kind === 'gap') {
479
619
  if (replicated.has(conversation))
480
620
  gap(conversation);
481
- const held = lives.get(conversation);
482
- if (held === undefined)
483
- return;
484
- setLive(conversation, { ...QUIET, turn: held.turn });
485
- emit(conversation);
621
+ if (lives.has(conversation))
622
+ unsent.set(conversation, new Set(FACTS));
486
623
  return;
487
624
  }
488
625
  if (event.kind === 'turn') {
@@ -505,23 +642,42 @@ export function createConversationStore(deps) {
505
642
  live(conversation, { options: structuredClone(event.options) });
506
643
  return;
507
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
+ }
508
657
  const arriving = acceptedMessage(event.message);
509
658
  if (arriving === null)
510
659
  return;
511
660
  if (replicated.has(conversation)) {
512
- void keep(conversation, arriving).catch(() => { });
661
+ if (deps.full()) {
662
+ gap(conversation);
663
+ return;
664
+ }
665
+ void keep(conversation, arriving).catch((error) => {
666
+ if (refusedForSpace(error))
667
+ gap(conversation);
668
+ });
513
669
  return;
514
670
  }
515
671
  if (sealTurns(conversation, new Set([arriving.turn])))
516
672
  emit(conversation);
517
673
  }
518
674
  async function kept(local, conversation, following) {
519
- if (following === null || (unwritten(following) && following.submitted === undefined)) {
675
+ if (following === null || empty(following)) {
520
676
  await local.dropChatDraft(conversation);
521
677
  drafts.delete(conversation);
522
678
  }
523
679
  else {
524
- await local.keepChatDraft(following);
680
+ await local.keepChatDrafts([following]);
525
681
  drafts.set(conversation, following);
526
682
  }
527
683
  }
@@ -534,13 +690,35 @@ export function createConversationStore(deps) {
534
690
  await kept(local, conversation, withSubmitted(held, left));
535
691
  return true;
536
692
  }
693
+ function moved(agent, operation, to) {
694
+ const from = newChat(agent);
695
+ return deps.durably(async (local) => {
696
+ const held = drafts.get(from);
697
+ const earlier = (held?.moves ?? []).filter((one) => !deps.expired(one));
698
+ if (earlier.includes(operation))
699
+ return;
700
+ const blank = {
701
+ conversation: from,
702
+ text: '',
703
+ revision: chatDraftRevision(),
704
+ };
705
+ const { moves: _answered, ...unsent } = held ?? blank;
706
+ const taken = drafts.has(to) || empty(unsent) ? [] : [{ ...unsent, conversation: to }];
707
+ const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
708
+ await local.keepChatDrafts([left, ...taken]);
709
+ drafts.set(from, left);
710
+ for (const one of taken)
711
+ drafts.set(to, one);
712
+ deps.publish(conversationChangeSet([from, to]));
713
+ });
714
+ }
537
715
  function withDraft(conversation, next) {
538
716
  return deps.durably(async (local) => {
539
717
  const following = await next(drafts.get(conversation), local);
540
718
  if (following === undefined)
541
719
  return;
542
720
  await kept(local, conversation, following);
543
- deps.publish(conversationChangeSet([], [conversation]));
721
+ deps.publish(conversationChangeSet([conversation]));
544
722
  });
545
723
  }
546
724
  async function submitted(conversation, operation, draft) {
@@ -552,17 +730,12 @@ export function createConversationStore(deps) {
552
730
  if (submission !== undefined)
553
731
  return undefined;
554
732
  const said = draft ?? current;
555
- submission = {
556
- operation,
557
- text: said?.text ?? '',
558
- files: [...(said?.files ?? [])],
559
- turn: null,
560
- };
733
+ submission = { operation, text: said?.text ?? '', turn: null };
561
734
  return {
562
735
  conversation,
563
736
  ...(draft === undefined || current === undefined
564
- ? { text: '', files: [], revision: chatDraftRevision() }
565
- : { text: current.text, files: current.files, revision: current.revision }),
737
+ ? { text: '', revision: chatDraftRevision() }
738
+ : { text: current.text, revision: current.revision }),
566
739
  submitted: [...(current?.submitted ?? []), submission],
567
740
  };
568
741
  });
@@ -580,10 +753,8 @@ export function createConversationStore(deps) {
580
753
  const dispatch = {};
581
754
  sending.set(submission.operation, dispatch);
582
755
  try {
583
- const files = await Promise.all(submission.files.map(async (file) => ({ ...(await declaredFile(file)), blob: file.blob })));
584
756
  const result = await invoke('conversation.send', conversation, {
585
757
  text: submission.text,
586
- files,
587
758
  operation_id: submission.operation,
588
759
  });
589
760
  if (result.kind === 'revoked-authorization')
@@ -592,7 +763,7 @@ export function createConversationStore(deps) {
592
763
  deps.unanswered();
593
764
  return result;
594
765
  }
595
- const turn = turnOf(result);
766
+ const turn = answered(result, 'turn');
596
767
  await withDraft(conversation, async (current, local) => {
597
768
  const held = current?.submitted?.find((one) => one.operation === submission.operation);
598
769
  if (current === undefined || held === undefined || held.turn !== null)
@@ -621,15 +792,37 @@ export function createConversationStore(deps) {
621
792
  return entries();
622
793
  },
623
794
  async read(conversation, options = {}) {
624
- const limit = limitOf(options);
625
- return deps.durably(async (local) => structuredClone(viewOf(conversation, await local.messageWindow(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
+ });
626
801
  },
627
- create(input) {
628
- 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;
629
813
  },
630
814
  async send(conversation, operation = changeBatchIdentity(), draft) {
815
+ if (deps.full())
816
+ return DURABLE_WRITE;
631
817
  if (deps.online()) {
632
- const { submission, earlier } = await submitted(conversation, operation, draft);
818
+ const held = await submitted(conversation, operation, draft).catch((error) => {
819
+ if (refusedForSpace(error))
820
+ return null;
821
+ throw error;
822
+ });
823
+ if (held === null)
824
+ return DURABLE_WRITE;
825
+ const { submission, earlier } = held;
633
826
  if (!earlier || !deps.expired(operation))
634
827
  return dispatched(conversation, submission);
635
828
  await forgotten(conversation, submission);
@@ -638,10 +831,19 @@ export function createConversationStore(deps) {
638
831
  if (draft !== undefined) {
639
832
  await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
640
833
  ? undefined
641
- : returned(current ?? { conversation, text: '', files: [], revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
834
+ : returned(current ?? { conversation, text: '', revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
642
835
  }
643
836
  return OFFLINE;
644
837
  },
838
+ async attach(room, file) {
839
+ if (!deps.online())
840
+ return OFFLINE;
841
+ return deps.call('conversation.attach', {
842
+ room,
843
+ ...(await declaredFile(file)),
844
+ blob: file.blob,
845
+ });
846
+ },
645
847
  answer(conversation, answer) {
646
848
  return invoke('conversation.answer', conversation, { answer });
647
849
  },
@@ -672,21 +874,22 @@ export function createConversationStore(deps) {
672
874
  return withDraft(conversation, (current) => ({
673
875
  conversation,
674
876
  text: draft.text,
675
- files: [...draft.files],
676
877
  revision,
677
- ...(current?.submitted === undefined ? {} : { submitted: current.submitted }),
878
+ ...(current === undefined ? {} : pending(current)),
879
+ }));
880
+ },
881
+ prepend(conversation, draft) {
882
+ return withDraft(conversation, (current) => ({
883
+ conversation,
884
+ ...(current === undefined ? { text: draft.text } : prepended(current, draft)),
885
+ revision: chatDraftRevision(),
886
+ ...(current === undefined ? {} : pending(current)),
678
887
  }));
679
888
  },
680
889
  discard(conversation) {
681
890
  return withDraft(conversation, (current) => current === undefined
682
891
  ? undefined
683
- : {
684
- conversation,
685
- text: '',
686
- files: [],
687
- revision: chatDraftRevision(),
688
- ...pending(current),
689
- });
892
+ : { conversation, text: '', revision: chatDraftRevision(), ...pending(current) });
690
893
  },
691
894
  };
692
895
  return {
@@ -713,7 +916,9 @@ export function createConversationStore(deps) {
713
916
  const named = new Set(listing.keys());
714
917
  const leaving = [
715
918
  ...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
716
- ].filter((conversation) => !named.has(conversation));
919
+ ].filter((conversation) => !named.has(conversation) &&
920
+ !creations.has(conversation) &&
921
+ (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
717
922
  for (const conversation of named) {
718
923
  if (!replicated.has(conversation))
719
924
  behind.add(conversation);
@@ -724,12 +929,25 @@ export function createConversationStore(deps) {
724
929
  await remove(leaving);
725
930
  },
726
931
  async catchUp(progress) {
727
- const reading = [...behind].filter((conversation) => replicated.has(conversation)).sort();
728
- if (reading.length > 0 && !deps.closed()) {
729
- const failed = await readHistories(reading);
932
+ if (deps.full()) {
933
+ if (behind.size > 0)
934
+ progress.complete = false;
935
+ return;
936
+ }
937
+ const owed = (conversation) => behind.has(conversation) && replicated.has(conversation);
938
+ const read = async (conversations) => {
939
+ if (conversations.length === 0 || deps.closed())
940
+ return;
941
+ const failed = await caughtUp(conversations);
730
942
  if (failed?.kind === 'transport-uncertainty')
731
943
  progress.reachable = false;
732
- }
944
+ };
945
+ const asking = [...behind].filter(owed).sort();
946
+ const awaited = asking.filter((conversation) => catching.has(conversation));
947
+ await Promise.all([
948
+ read(asking.filter((conversation) => !catching.has(conversation))),
949
+ Promise.all(awaited.map((conversation) => catching.get(conversation))).then(() => read(awaited.filter((conversation) => owed(conversation) && !catching.has(conversation)))),
950
+ ]);
733
951
  if (behind.size > 0)
734
952
  progress.complete = false;
735
953
  },
@@ -771,27 +989,38 @@ export function createConversationStore(deps) {
771
989
  reopened() {
772
990
  sending.clear();
773
991
  },
992
+ lost() {
993
+ catching.clear();
994
+ },
774
995
  watch(conversation, options, deliver) {
775
- const limit = limitOf(options);
776
- const key = JSON.stringify([conversation, limit ?? null]);
996
+ const asked = askedOf(options);
997
+ const key = JSON.stringify([conversation, asked.limit, asked.after ?? null, asked.total]);
777
998
  const view = views.get(key) ?? {
778
999
  conversation,
779
- limit,
1000
+ asked,
780
1001
  watchers: new Set(),
781
1002
  window: null,
1003
+ more: false,
1004
+ total: null,
782
1005
  };
783
1006
  views.set(key, view);
784
1007
  const watcher = { notify: deliver, ready: false };
785
1008
  view.watchers.add(watcher);
786
1009
  void deps
787
1010
  .durably(async (local) => {
788
- view.window ??= await local.messageWindow(conversation, limit);
1011
+ if (view.window !== null)
1012
+ return;
1013
+ const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
1014
+ view.window = messages;
1015
+ view.more = more;
1016
+ view.total = asked.total ? await local.messageCount(conversation) : null;
789
1017
  })
790
1018
  .then(() => {
791
1019
  if (deps.closed() || !view.watchers.has(watcher))
792
1020
  return;
793
1021
  watcher.ready = true;
794
- watcher.notify(viewOf(conversation, view.window ?? []));
1022
+ const window = view.window ?? [];
1023
+ watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
795
1024
  })
796
1025
  .catch(() => { });
797
1026
  return () => {
@@ -838,12 +1067,16 @@ export function createConversationStore(deps) {
838
1067
  if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
839
1068
  if (event.kind === 'list') {
840
1069
  listing.clear();
841
- for (const entry of event.entries)
1070
+ for (const entry of event.entries) {
842
1071
  listing.set(entry.conversation, { ...entry });
1072
+ creations.delete(entry.conversation);
1073
+ }
843
1074
  listKnown = true;
1075
+ openingEnded();
844
1076
  }
845
1077
  else if (event.kind === 'listed') {
846
1078
  listing.set(event.entry.conversation, { ...event.entry });
1079
+ creations.delete(event.entry.conversation);
847
1080
  }
848
1081
  else {
849
1082
  listing.delete(event.conversation);
@@ -866,6 +1099,7 @@ export function createConversationStore(deps) {
866
1099
  close() {
867
1100
  views.clear();
868
1101
  lives.clear();
1102
+ unsent.clear();
869
1103
  listWatchers.clear();
870
1104
  agentWatchers.clear();
871
1105
  },