@agentshouse/mdruntime 0.1.0-cde2b99d2fcbc → 0.1.0-cdf06805b9658

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