@agentshouse/mdruntime 0.1.0-ca68e8b4bbebc → 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 (233) hide show
  1. package/README.md +19 -16
  2. package/dist/adapter.d.ts +142 -70
  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 +26 -11
  11. package/dist/bootstrap.d.ts.map +1 -1
  12. package/dist/bootstrap.js +181 -103
  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 -89
  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 +19 -10
  23. package/dist/client.d.ts.map +1 -1
  24. package/dist/client.js +86 -9
  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 +28 -17
  32. package/dist/connection.d.ts.map +1 -1
  33. package/dist/connection.js +3147 -1396
  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 +29 -14
  38. package/dist/conversation.d.ts.map +1 -1
  39. package/dist/conversation.js +423 -198
  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 +3 -10
  53. package/dist/host.d.ts.map +1 -1
  54. package/dist/host.js +65 -39
  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 +19 -13
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +8 -2
  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 +12 -6
  71. package/dist/memory.d.ts.map +1 -1
  72. package/dist/memory.js +202 -103
  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 +7 -3
  77. package/dist/model.d.ts.map +1 -1
  78. package/dist/model.js +7 -13
  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 +2 -5
  92. package/dist/overlay.d.ts.map +1 -1
  93. package/dist/overlay.js +87 -172
  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 +16 -190
  98. package/dist/query.d.ts.map +1 -1
  99. package/dist/query.js +3 -470
  100. package/dist/reach.d.ts +17 -8
  101. package/dist/reach.d.ts.map +1 -1
  102. package/dist/reach.js +219 -90
  103. package/dist/react.d.ts +4 -3
  104. package/dist/react.d.ts.map +1 -1
  105. package/dist/react.js +2 -14
  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 +18 -15
  113. package/dist/refresh.d.ts.map +1 -1
  114. package/dist/refresh.js +155 -58
  115. package/dist/replica.d.ts +56 -33
  116. package/dist/replica.d.ts.map +1 -1
  117. package/dist/replica.js +301 -191
  118. package/dist/room-draft.d.ts +23 -4
  119. package/dist/room-draft.d.ts.map +1 -1
  120. package/dist/room-draft.js +158 -71
  121. package/dist/rows.d.ts +22 -30
  122. package/dist/rows.d.ts.map +1 -1
  123. package/dist/rows.js +63 -114
  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 +12 -82
  128. package/dist/search.d.ts.map +1 -1
  129. package/dist/search.js +10 -207
  130. package/dist/seed.d.ts.map +1 -1
  131. package/dist/seed.js +18 -4
  132. package/dist/shared.d.ts +7 -3
  133. package/dist/shared.d.ts.map +1 -1
  134. package/dist/shared.js +119 -66
  135. package/dist/status.d.ts +4 -3
  136. package/dist/status.d.ts.map +1 -1
  137. package/dist/status.js +3 -1
  138. package/dist/store.d.ts +92 -98
  139. package/dist/store.d.ts.map +1 -1
  140. package/dist/store.js +330 -675
  141. package/dist/stored.d.ts +162 -0
  142. package/dist/stored.d.ts.map +1 -0
  143. package/dist/stored.js +543 -0
  144. package/dist/tables.d.ts +353 -0
  145. package/dist/tables.d.ts.map +1 -0
  146. package/dist/tables.js +588 -0
  147. package/dist/transfer.d.ts +0 -5
  148. package/dist/transfer.d.ts.map +1 -1
  149. package/dist/turn.d.ts +5 -3
  150. package/dist/turn.d.ts.map +1 -1
  151. package/dist/turn.js +51 -21
  152. package/dist/validation.d.ts +29 -0
  153. package/dist/validation.d.ts.map +1 -0
  154. package/dist/validation.js +104 -0
  155. package/dist/windows.d.ts +11 -0
  156. package/dist/windows.d.ts.map +1 -0
  157. package/dist/windows.js +16 -0
  158. package/dist/wire.d.ts +34 -5
  159. package/dist/wire.d.ts.map +1 -1
  160. package/dist/wire.js +10 -2
  161. package/package.json +18 -19
  162. package/AGENTS.md +0 -299
  163. package/dist/acquisition.d.ts +0 -14
  164. package/dist/acquisition.d.ts.map +0 -1
  165. package/dist/acquisition.js +0 -24
  166. package/dist/chat/acts.d.ts +0 -33
  167. package/dist/chat/acts.d.ts.map +0 -1
  168. package/dist/chat/acts.js +0 -36
  169. package/dist/chat/context.d.ts +0 -3
  170. package/dist/chat/context.d.ts.map +0 -1
  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 -61
  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 -27
  181. package/dist/chat/list.d.ts.map +0 -1
  182. package/dist/chat/list.js +0 -63
  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 -22
  190. package/dist/chat/messages.d.ts.map +0 -1
  191. package/dist/chat/messages.js +0 -155
  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 -629
  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 -34
  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 -642
  231. package/dist/reads.d.ts +0 -11
  232. package/dist/reads.d.ts.map +0 -1
  233. package/dist/reads.js +0 -98
@@ -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,13 +34,19 @@ 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';
26
48
  const NEW_CHAT = 'new:';
49
+ export const HISTORY_CONVERSATIONS = 64;
27
50
  export function newChat(agent) {
28
51
  return `${NEW_CHAT}${agent}`;
29
52
  }
@@ -82,19 +105,25 @@ function appliedTurn(held, operation) {
82
105
  parts: held.parts.map((kept, at) => (at === operation.index ? changed : kept)),
83
106
  };
84
107
  }
85
- function ordered(left, right) {
86
- if (left.position !== right.position)
87
- return left.position < right.position ? -1 : 1;
88
- if (left.identity !== right.identity)
89
- return left.identity < right.identity ? -1 : 1;
90
- return 0;
91
- }
92
108
  function windowOf(held, arriving, limit) {
93
109
  const byIdentity = new Map(held.map((message) => [message.identity, message]));
94
110
  for (const message of arriving)
95
111
  byIdentity.set(message.identity, message);
96
- const next = [...byIdentity.values()].sort(ordered);
97
- 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
+ };
98
127
  }
99
128
  function newestOf(arriving, from) {
100
129
  return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
@@ -165,18 +194,42 @@ function answered(result, field) {
165
194
  const value = answer[field];
166
195
  return typeof value === 'string' ? value : null;
167
196
  }
168
- function limitOf(options) {
169
- const { limit } = options;
170
- if (limit === undefined)
171
- return undefined;
172
- if (!Number.isInteger(limit) || limit < 0) {
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];
208
+ }
209
+ function askedOf(options) {
210
+ const { limit, after, total } = options;
211
+ if (limit !== undefined && (!Number.isInteger(limit) || limit < 0)) {
173
212
  throw runtimeError('invalid-limit', `conversation limit must be a non-negative integer, got ${String(limit)}`);
174
213
  }
175
- 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;
176
223
  }
177
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
+ };
178
231
  function unwritten(draft) {
179
- return draft.text === '' && draft.files.length === 0;
232
+ return draft.text === '';
180
233
  }
181
234
  function pending(draft) {
182
235
  return {
@@ -191,11 +244,13 @@ function withSubmitted(draft, submitted) {
191
244
  const { submitted: _held, ...unsent } = draft;
192
245
  return submitted.length === 0 ? unsent : { ...unsent, submitted };
193
246
  }
247
+ function prepended(draft, said) {
248
+ return { text: [said.text, draft.text].filter((one) => one !== '').join('\n') };
249
+ }
194
250
  function returned(draft, submission, others) {
195
251
  return withSubmitted({
196
252
  conversation: draft.conversation,
197
- text: [submission.text, draft.text].filter((one) => one !== '').join('\n'),
198
- files: [...submission.files, ...draft.files],
253
+ ...prepended(draft, submission),
199
254
  revision: chatDraftRevision(),
200
255
  }, others);
201
256
  }
@@ -209,21 +264,28 @@ export function createConversationStore(deps) {
209
264
  const catching = new Map();
210
265
  const gaps = new Map();
211
266
  const lives = new Map();
267
+ const unsent = new Map();
212
268
  const views = new Map();
213
269
  const listing = new Map();
270
+ let relisting = null;
271
+ let listCursor = null;
214
272
  const creations = new Set();
215
273
  const listWatchers = new Set();
274
+ const unlisted = new Set();
216
275
  let listKnown = false;
276
+ let listHeld = false;
277
+ let listKept = false;
217
278
  const agentWatchers = new Set();
218
- let agentNames = new Map();
219
- let retiredAgents = new Set();
220
- let agentsRead = null;
279
+ const unnamed = new Set();
280
+ let agentsShown = null;
281
+ let listedAgents = null;
282
+ let agentsReading = false;
221
283
  let replicated = new Set();
222
284
  let handedReplicated;
223
285
  function liveOf(conversation) {
224
286
  return lives.get(conversation) ?? QUIET;
225
287
  }
226
- function viewOf(conversation, held) {
288
+ function viewOf(conversation, held, frame) {
227
289
  const draft = drafts.get(conversation);
228
290
  const facts = liveOf(conversation);
229
291
  return {
@@ -234,14 +296,20 @@ export function createConversationStore(deps) {
234
296
  text,
235
297
  message,
236
298
  })),
299
+ ...frame,
237
300
  turn: structuredClone(facts.turn),
238
301
  questions: structuredClone(facts.questions),
239
302
  process: facts.process === null ? null : { ...facts.process },
303
+ processEnded: listing.get(conversation)?.processEnded ?? null,
240
304
  commands: structuredClone(facts.commands),
241
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],
242
310
  chatDraft: draft === undefined || unwritten(draft)
243
311
  ? null
244
- : { text: draft.text, files: [...draft.files], revision: draft.revision },
312
+ : { text: draft.text, revision: draft.revision },
245
313
  };
246
314
  }
247
315
  function emit(conversation) {
@@ -253,21 +321,36 @@ export function createConversationStore(deps) {
253
321
  const ready = [...view.watchers].filter((watcher) => watcher.ready);
254
322
  if (ready.length === 0)
255
323
  continue;
256
- const value = viewOf(conversation, view.window);
324
+ const value = viewOf(conversation, view.window, sharedFrame(view, view.window));
257
325
  for (const watcher of ready)
258
326
  watcher.notify(value);
259
327
  }
260
328
  }
261
- 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;
262
334
  for (const view of views.values()) {
263
335
  if (view.conversation !== conversation || view.window === null)
264
336
  continue;
265
- 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
+ }
266
352
  }
267
353
  }
268
- function changesOf(conversation, arriving) {
269
- return arriving.map(({ identity, text }) => ({ conversation, identity, text }));
270
- }
271
354
  function stateOf(conversation) {
272
355
  const facts = liveOf(conversation);
273
356
  if (facts.questions.length > 0)
@@ -277,34 +360,113 @@ export function createConversationStore(deps) {
277
360
  function entries() {
278
361
  return [...listing.values()].map((entry) => ({
279
362
  conversation: entry.conversation,
280
- agent: { identity: entry.agent, name: agentNames.get(entry.agent) ?? null },
363
+ agent: { identity: entry.agent, name: entry.agentName },
281
364
  name: entry.name,
282
365
  created: entry.created,
283
366
  finished: entry.finished,
284
367
  outcome: entry.outcome,
368
+ waiting: entry.waiting,
285
369
  state: stateOf(entry.conversation),
286
370
  started: lives.get(entry.conversation)?.turn?.started ?? null,
287
371
  }));
288
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
+ }
289
398
  function emitList() {
290
- if (deps.closed() || listWatchers.size === 0)
399
+ if (deps.closed())
291
400
  return;
292
- const value = entries();
293
- for (const watcher of listWatchers)
294
- 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
+ }
295
430
  }
296
431
  function agents() {
297
- return [...agentNames]
298
- .filter(([identity]) => !retiredAgents.has(identity))
299
- .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 }))
300
441
  .sort((left, right) => left.name === right.name
301
442
  ? left.identity.localeCompare(right.identity)
302
443
  : left.name.localeCompare(right.name));
303
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
+ }
304
462
  function emitAgents() {
305
463
  if (deps.closed() || agentWatchers.size === 0)
306
464
  return;
307
465
  const value = agents();
466
+ const shown = JSON.stringify(value);
467
+ if (shown === agentsShown)
468
+ return;
469
+ agentsShown = shown;
308
470
  for (const watcher of agentWatchers)
309
471
  watcher(value);
310
472
  }
@@ -320,53 +482,42 @@ export function createConversationStore(deps) {
320
482
  if (stateOf(conversation) !== before || next?.turn?.started !== began)
321
483
  emitList();
322
484
  }
323
- async function readAgents() {
324
- const declaration = deps.agents();
325
- if (declaration === undefined)
326
- return;
327
- const held = await deps.documents(declaration);
328
- if ('pending' in held)
329
- return;
330
- const { documents } = held;
331
- const names = new Map();
332
- const retired = new Set();
333
- for (const document of documents) {
334
- if (document.type !== declaration.type)
335
- continue;
336
- const identity = document.fields?.[declaration.identity];
337
- const name = document.fields?.[declaration.name];
338
- if (typeof identity !== 'string' || typeof name !== 'string')
339
- continue;
340
- names.set(identity, name);
341
- const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
342
- if (ended !== undefined && ended !== null)
343
- retired.add(identity);
344
- }
345
- const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
346
- if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents]))
347
- return;
348
- agentNames = names;
349
- retiredAgents = retired;
350
- if (named)
351
- emitList();
352
- emitAgents();
353
- const ended = [...drafts.keys()].filter(retiredChat);
354
- if (ended.length > 0)
355
- await remove(ended);
356
- }
357
485
  function retiredChat(conversation) {
358
- return (conversation.startsWith(NEW_CHAT) && retiredAgents.has(conversation.slice(NEW_CHAT.length)));
359
- }
360
- function agentsReady() {
361
- agentsRead ??= readAgents().catch(() => {
362
- agentsRead = null;
363
- });
364
- return agentsRead;
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);
365
490
  }
366
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);
367
495
  setLive(conversation, { ...liveOf(conversation), ...change });
368
496
  emit(conversation);
369
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
+ }
370
521
  function relayed(conversation, turn, operation) {
371
522
  const held = liveOf(conversation).turn;
372
523
  const next = held?.turn === turn ? appliedTurn(held, operation) : startedTurn(turn, operation);
@@ -393,12 +544,6 @@ export function createConversationStore(deps) {
393
544
  }
394
545
  async function remove(leaving) {
395
546
  await deps.durably(async (local) => {
396
- const removed = [];
397
- for (const conversation of leaving) {
398
- for (const identity of await local.messageIdentities(conversation)) {
399
- removed.push({ conversation, identity, text: null });
400
- }
401
- }
402
547
  await local.dropConversations(leaving);
403
548
  const changed = [];
404
549
  for (const conversation of leaving) {
@@ -410,13 +555,28 @@ export function createConversationStore(deps) {
410
555
  partials.delete(conversation);
411
556
  drafts.delete(conversation);
412
557
  behind.delete(conversation);
558
+ unsent.delete(conversation);
413
559
  setLive(conversation, null);
414
- slide(conversation, null);
560
+ await slide(local, conversation, null);
415
561
  }
416
562
  if (changed.length > 0)
417
- deps.publish(conversationChangeSet(removed, changed));
563
+ deps.publish(conversationChangeSet(changed));
418
564
  });
419
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
+ }
420
580
  function gap(conversation) {
421
581
  gaps.set(conversation, (gaps.get(conversation) ?? 0) + 1);
422
582
  behind.add(conversation);
@@ -439,18 +599,19 @@ export function createConversationStore(deps) {
439
599
  if (advances)
440
600
  reads.set(conversation, message.position);
441
601
  sealTurns(conversation, new Set([message.turn]));
442
- slide(conversation, [message]);
443
- deps.publish(conversationChangeSet(changesOf(conversation, [message]), [conversation]));
602
+ await slide(local, conversation, [message]);
603
+ deps.publish(conversationChangeSet([conversation]));
444
604
  });
445
605
  }
446
606
  async function readHistories(conversations, ended) {
447
607
  const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
448
- let asking = conversations.map((conversation) => ({
608
+ let waiting = conversations.map((conversation) => ({
449
609
  conversation,
450
610
  after: reads.get(conversation) ?? undefined,
451
611
  }));
452
- while (asking.length > 0) {
453
- 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({
454
615
  conversations: asking.map(({ conversation, after }) => ({
455
616
  conversation,
456
617
  ...(after === undefined ? {} : { after }),
@@ -473,7 +634,7 @@ export function createConversationStore(deps) {
473
634
  if (!following.some((next) => next.conversation === conversation))
474
635
  ended(conversation);
475
636
  }
476
- asking = following;
637
+ waiting = [...waiting.slice(HISTORY_CONVERSATIONS), ...following];
477
638
  }
478
639
  return null;
479
640
  }
@@ -514,9 +675,9 @@ export function createConversationStore(deps) {
514
675
  sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
515
676
  if (whole && (gaps.get(conversation) ?? 0) === generation)
516
677
  behind.delete(conversation);
517
- slide(conversation, arriving);
678
+ await slide(local, conversation, arriving);
518
679
  if (arriving.length > 0 || sealing || whole) {
519
- deps.publish(conversationChangeSet(changesOf(conversation, arriving), [conversation]));
680
+ deps.publish(conversationChangeSet([conversation]));
520
681
  }
521
682
  });
522
683
  }
@@ -525,11 +686,8 @@ export function createConversationStore(deps) {
525
686
  if (event.kind === 'gap') {
526
687
  if (replicated.has(conversation))
527
688
  gap(conversation);
528
- const held = lives.get(conversation);
529
- if (held === undefined)
530
- return;
531
- setLive(conversation, { ...QUIET, turn: held.turn });
532
- emit(conversation);
689
+ if (lives.has(conversation))
690
+ unsent.set(conversation, new Set(FACTS));
533
691
  return;
534
692
  }
535
693
  if (event.kind === 'turn') {
@@ -552,11 +710,35 @@ export function createConversationStore(deps) {
552
710
  live(conversation, { options: structuredClone(event.options) });
553
711
  return;
554
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
+ }
555
729
  const arriving = acceptedMessage(event.message);
556
730
  if (arriving === null)
557
731
  return;
732
+ noted(conversation, arriving);
558
733
  if (replicated.has(conversation)) {
559
- 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
+ });
560
742
  return;
561
743
  }
562
744
  if (sealTurns(conversation, new Set([arriving.turn])))
@@ -591,7 +773,6 @@ export function createConversationStore(deps) {
591
773
  const blank = {
592
774
  conversation: from,
593
775
  text: '',
594
- files: [],
595
776
  revision: chatDraftRevision(),
596
777
  };
597
778
  const { moves: _answered, ...unsent } = held ?? blank;
@@ -601,7 +782,7 @@ export function createConversationStore(deps) {
601
782
  drafts.set(from, left);
602
783
  for (const one of taken)
603
784
  drafts.set(to, one);
604
- deps.publish(conversationChangeSet([], [from, to]));
785
+ deps.publish(conversationChangeSet([from, to]));
605
786
  });
606
787
  }
607
788
  function withDraft(conversation, next) {
@@ -610,7 +791,7 @@ export function createConversationStore(deps) {
610
791
  if (following === undefined)
611
792
  return;
612
793
  await kept(local, conversation, following);
613
- deps.publish(conversationChangeSet([], [conversation]));
794
+ deps.publish(conversationChangeSet([conversation]));
614
795
  });
615
796
  }
616
797
  async function submitted(conversation, operation, draft) {
@@ -622,17 +803,12 @@ export function createConversationStore(deps) {
622
803
  if (submission !== undefined)
623
804
  return undefined;
624
805
  const said = draft ?? current;
625
- submission = {
626
- operation,
627
- text: said?.text ?? '',
628
- files: [...(said?.files ?? [])],
629
- turn: null,
630
- };
806
+ submission = { operation, text: said?.text ?? '', turn: null };
631
807
  return {
632
808
  conversation,
633
809
  ...(draft === undefined || current === undefined
634
- ? { text: '', files: [], revision: chatDraftRevision() }
635
- : { text: current.text, files: current.files, revision: current.revision }),
810
+ ? { text: '', revision: chatDraftRevision() }
811
+ : { text: current.text, revision: current.revision }),
636
812
  submitted: [...(current?.submitted ?? []), submission],
637
813
  };
638
814
  });
@@ -647,22 +823,21 @@ export function createConversationStore(deps) {
647
823
  });
648
824
  }
649
825
  async function dispatched(conversation, submission) {
650
- const dispatch = {};
826
+ const dispatch = { answered: false };
651
827
  sending.set(submission.operation, dispatch);
652
828
  try {
653
- const files = await Promise.all(submission.files.map(async (file) => ({ ...(await declaredFile(file)), blob: file.blob })));
654
- const result = await invoke('conversation.send', conversation, {
655
- text: submission.text,
656
- files,
829
+ const result = await invoke('conversations/send', conversation, {
657
830
  operation_id: submission.operation,
831
+ text: submission.text,
658
832
  });
833
+ dispatch.answered = result.kind !== 'transport-uncertainty';
659
834
  if (result.kind === 'revoked-authorization')
660
835
  return result;
661
836
  if (result.kind !== 'action' && result.retryable) {
662
837
  deps.unanswered();
663
838
  return result;
664
839
  }
665
- const turn = answered(result, 'turn');
840
+ const turn = answered(result, 'message_id');
666
841
  await withDraft(conversation, async (current, local) => {
667
842
  const held = current?.submitted?.find((one) => one.operation === submission.operation);
668
843
  if (current === undefined || held === undefined || held.turn !== null)
@@ -682,21 +857,34 @@ export function createConversationStore(deps) {
682
857
  sending.delete(submission.operation);
683
858
  }
684
859
  }
685
- function invoke(name, conversation, extra = {}) {
686
- return deps.call(name, { conversation, ...extra });
860
+ function invoke(name, conversation, extra = { operation_id: changeBatchIdentity() }) {
861
+ return deps.call(name, { conversation_id: conversation, ...extra });
687
862
  }
688
863
  const surface = {
689
- async list() {
690
- await agentsReady();
691
- 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
+ });
692
874
  },
693
875
  async read(conversation, options = {}) {
694
- const limit = limitOf(options);
695
- 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
+ });
696
884
  },
697
885
  async create(input) {
698
- const result = await deps.call('conversation.create', input);
699
- const conversation = answered(result, 'conversation');
886
+ const result = await deps.call('conversations/create', input);
887
+ const conversation = answered(result, 'conversation_id');
700
888
  if (conversation === null)
701
889
  return result;
702
890
  if (!listing.has(conversation))
@@ -707,8 +895,17 @@ export function createConversationStore(deps) {
707
895
  return result;
708
896
  },
709
897
  async send(conversation, operation = changeBatchIdentity(), draft) {
898
+ if (deps.full())
899
+ return DURABLE_WRITE;
710
900
  if (deps.online()) {
711
- 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;
712
909
  if (!earlier || !deps.expired(operation))
713
910
  return dispatched(conversation, submission);
714
911
  await forgotten(conversation, submission);
@@ -717,33 +914,24 @@ export function createConversationStore(deps) {
717
914
  if (draft !== undefined) {
718
915
  await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
719
916
  ? undefined
720
- : returned(current ?? { conversation, text: '', files: [], revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
917
+ : returned(current ?? { conversation, text: '', revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
721
918
  }
722
919
  return OFFLINE;
723
920
  },
724
921
  answer(conversation, answer) {
725
- return invoke('conversation.answer', conversation, { answer });
922
+ return invoke('conversations/interaction/answer', conversation, {
923
+ operation_id: changeBatchIdentity(),
924
+ ...answer,
925
+ });
726
926
  },
727
927
  interrupt(conversation) {
728
- return invoke('conversation.interrupt', conversation);
928
+ return invoke('conversations/interrupt', conversation);
729
929
  },
730
930
  kill(conversation) {
731
- return invoke('conversation.kill', conversation);
732
- },
733
- async cancel(conversation, turn) {
734
- const result = await invoke('conversation.cancel', conversation, { turn });
735
- if (result.kind !== 'action')
736
- return result;
737
- await withDraft(conversation, (current) => {
738
- const held = current?.submitted?.find((one) => one.turn === turn);
739
- if (current === undefined || held === undefined)
740
- return undefined;
741
- return returned(current, held, current.submitted.filter((one) => one !== held));
742
- });
743
- return result;
931
+ return invoke('conversations/kill', conversation);
744
932
  },
745
933
  delete(conversation) {
746
- return invoke('conversation.delete', conversation);
934
+ return invoke('conversations/delete', conversation);
747
935
  },
748
936
  };
749
937
  const chatDraft = {
@@ -751,27 +939,36 @@ export function createConversationStore(deps) {
751
939
  return withDraft(conversation, (current) => ({
752
940
  conversation,
753
941
  text: draft.text,
754
- files: [...draft.files],
755
942
  revision,
756
943
  ...(current === undefined ? {} : pending(current)),
757
944
  }));
758
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)),
952
+ }));
953
+ },
759
954
  discard(conversation) {
760
955
  return withDraft(conversation, (current) => current === undefined
761
956
  ? undefined
762
- : {
763
- conversation,
764
- text: '',
765
- files: [],
766
- revision: chatDraftRevision(),
767
- ...pending(current),
768
- });
957
+ : { conversation, text: '', revision: chatDraftRevision(), ...pending(current) });
769
958
  },
770
959
  };
771
960
  return {
772
961
  surface,
773
962
  chatDraft,
774
- 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
+ }
775
972
  stored.clear();
776
973
  reads.clear();
777
974
  partials.clear();
@@ -785,6 +982,8 @@ export function createConversationStore(deps) {
785
982
  drafts.clear();
786
983
  for (const record of kept)
787
984
  drafts.set(record.conversation, record);
985
+ if (listHeld)
986
+ learned();
788
987
  },
789
988
  async reconcile() {
790
989
  if (!listKnown)
@@ -796,7 +995,7 @@ export function createConversationStore(deps) {
796
995
  !creations.has(conversation) &&
797
996
  (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
798
997
  for (const conversation of named) {
799
- if (!replicated.has(conversation))
998
+ if (!replicated.has(conversation) && missed(conversation))
800
999
  behind.add(conversation);
801
1000
  }
802
1001
  replicated = named;
@@ -805,6 +1004,11 @@ export function createConversationStore(deps) {
805
1004
  await remove(leaving);
806
1005
  },
807
1006
  async catchUp(progress) {
1007
+ if (deps.full()) {
1008
+ if (behind.size > 0)
1009
+ progress.complete = false;
1010
+ return;
1011
+ }
808
1012
  const owed = (conversation) => behind.has(conversation) && replicated.has(conversation);
809
1013
  const read = async (conversations) => {
810
1014
  if (conversations.length === 0 || deps.closed())
@@ -853,40 +1057,48 @@ export function createConversationStore(deps) {
853
1057
  known() {
854
1058
  return listKnown;
855
1059
  },
856
- reconnected() {
857
- for (const conversation of replicated)
858
- gap(conversation);
859
- },
860
1060
  reopened() {
861
- sending.clear();
1061
+ relisting = null;
1062
+ for (const [operation, dispatch] of sending)
1063
+ if (!dispatch.answered)
1064
+ sending.delete(operation);
862
1065
  },
863
1066
  lost() {
864
1067
  catching.clear();
865
1068
  },
866
1069
  watch(conversation, options, deliver) {
867
- const limit = limitOf(options);
868
- 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]);
869
1072
  const view = views.get(key) ?? {
870
1073
  conversation,
871
- limit,
1074
+ asked,
872
1075
  watchers: new Set(),
873
1076
  window: null,
1077
+ more: false,
1078
+ total: null,
874
1079
  };
875
1080
  views.set(key, view);
876
1081
  const watcher = { notify: deliver, ready: false };
877
1082
  view.watchers.add(watcher);
878
- void deps
879
- .durably(async (local) => {
880
- view.window ??= await local.messageWindow(conversation, limit);
881
- })
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)
882
1092
  .then(() => {
883
1093
  if (deps.closed() || !view.watchers.has(watcher))
884
1094
  return;
885
1095
  watcher.ready = true;
886
- watcher.notify(viewOf(conversation, view.window ?? []));
1096
+ const window = view.window ?? [];
1097
+ watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
887
1098
  })
888
1099
  .catch(() => { });
889
1100
  return () => {
1101
+ reading.abort();
890
1102
  if (!view.watchers.delete(watcher))
891
1103
  return;
892
1104
  if (view.watchers.size === 0)
@@ -894,75 +1106,88 @@ export function createConversationStore(deps) {
894
1106
  };
895
1107
  },
896
1108
  watchList(deliver) {
897
- listWatchers.add(deliver);
898
- void agentsReady().then(() => {
899
- if (!deps.closed() && listWatchers.has(deliver))
900
- deliver(entries());
901
- });
1109
+ unlisted.add(deliver);
1110
+ learned();
902
1111
  return () => {
1112
+ unlisted.delete(deliver);
903
1113
  listWatchers.delete(deliver);
904
1114
  };
905
1115
  },
906
1116
  watchAgents(deliver) {
907
- agentWatchers.add(deliver);
908
- void agentsReady().then(() => {
909
- if (!deps.closed() && agentWatchers.has(deliver))
910
- deliver(agents());
911
- });
1117
+ unnamed.add(deliver);
1118
+ learned();
1119
+ readAgents();
912
1120
  return () => {
1121
+ unnamed.delete(deliver);
913
1122
  agentWatchers.delete(deliver);
914
1123
  };
915
1124
  },
1125
+ learned,
916
1126
  changed(set) {
917
1127
  for (const conversation of set.conversations)
918
1128
  emit(conversation);
919
- const declaration = deps.agents();
920
- if (declaration !== undefined &&
921
- set.documents.some((change) => change.locator === declaration.locator)) {
922
- agentsRead = readAgents().catch(() => {
923
- agentsRead = null;
924
- });
925
- }
926
1129
  },
927
1130
  receive(event) {
928
1131
  if (deps.closed())
929
1132
  return false;
930
1133
  if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
931
1134
  if (event.kind === 'list') {
932
- listing.clear();
1135
+ if (event.whole)
1136
+ relisting ??= new Map();
933
1137
  for (const entry of event.entries) {
934
- listing.set(entry.conversation, { ...entry });
935
- creations.delete(entry.conversation);
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;
936
1150
  }
1151
+ listCursor = event.cursor;
1152
+ deps.stream()?.hold(event.cursor);
937
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();
938
1163
  }
939
1164
  else if (event.kind === 'listed') {
940
- listing.set(event.entry.conversation, { ...event.entry });
941
- creations.delete(event.entry.conversation);
1165
+ listed(event.entry);
1166
+ relisting?.set(event.entry.conversation, { ...event.entry });
942
1167
  }
943
1168
  else {
944
1169
  listing.delete(event.conversation);
1170
+ relisting?.delete(event.conversation);
945
1171
  }
1172
+ keepList();
946
1173
  emitList();
947
- return (event.kind !== 'list' &&
1174
+ return ((event.kind !== 'list' &&
948
1175
  listKnown &&
949
1176
  (listing.size !== replicated.size ||
950
- [...listing.keys()].some((conversation) => !replicated.has(conversation))));
1177
+ [...listing.keys()].some((conversation) => !replicated.has(conversation)))) ||
1178
+ (listKnown && [...drafts.keys()].some(retiredChat)));
951
1179
  }
952
1180
  heard(event);
953
1181
  return false;
954
1182
  },
955
- searchable() {
956
- return [...(listKnown ? listing.keys() : stored)].sort();
957
- },
958
- acquired(conversation) {
959
- return reads.has(conversation) && !partials.has(conversation);
960
- },
961
1183
  close() {
962
1184
  views.clear();
963
1185
  lives.clear();
1186
+ unsent.clear();
964
1187
  listWatchers.clear();
1188
+ unlisted.clear();
965
1189
  agentWatchers.clear();
1190
+ unnamed.clear();
966
1191
  },
967
1192
  };
968
1193
  }