@agentshouse/mdruntime 0.1.0-ca4dc698bd0c4 → 0.1.0-ca69b8fe1fa9f

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