@agentshouse/mdruntime 0.1.0-cb0eb0d87ff49 → 0.1.0-cb122f2ba154d

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