@agentshouse/mdruntime 0.1.0-cf24798931067 → 0.1.0-cf53a3b60b194

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