@agentshouse/mdruntime 0.1.0-ce531b51ff088 → 0.1.0-ceb97106cc2fd

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 (203) hide show
  1. package/AGENTS.md +273 -175
  2. package/README.md +21 -11
  3. package/dist/acquisition.d.ts +14 -0
  4. package/dist/acquisition.d.ts.map +1 -0
  5. package/dist/acquisition.js +24 -0
  6. package/dist/adapter.d.ts +129 -41
  7. package/dist/adapter.d.ts.map +1 -1
  8. package/dist/adapter.js +1 -9
  9. package/dist/attached-model.d.ts +11 -0
  10. package/dist/attached-model.d.ts.map +1 -0
  11. package/dist/attached-model.js +47 -0
  12. package/dist/attempt.d.ts +16 -0
  13. package/dist/attempt.d.ts.map +1 -0
  14. package/dist/attempt.js +70 -0
  15. package/dist/binary.d.ts +23 -0
  16. package/dist/binary.d.ts.map +1 -0
  17. package/dist/binary.js +8 -0
  18. package/dist/bootstrap.d.ts +20 -6
  19. package/dist/bootstrap.d.ts.map +1 -1
  20. package/dist/bootstrap.js +114 -37
  21. package/dist/change-set.d.ts +2 -0
  22. package/dist/change-set.d.ts.map +1 -1
  23. package/dist/change-set.js +49 -16
  24. package/dist/change.d.ts +15 -0
  25. package/dist/change.d.ts.map +1 -1
  26. package/dist/change.js +26 -1
  27. package/dist/chat/acts.d.ts +33 -0
  28. package/dist/chat/acts.d.ts.map +1 -0
  29. package/dist/chat/acts.js +36 -0
  30. package/dist/chat/context.d.ts +3 -0
  31. package/dist/chat/context.d.ts.map +1 -0
  32. package/dist/chat/context.js +10 -0
  33. package/dist/chat/input.d.ts +9 -0
  34. package/dist/chat/input.d.ts.map +1 -0
  35. package/dist/chat/input.js +99 -0
  36. package/dist/chat/kit.d.ts +61 -0
  37. package/dist/chat/kit.d.ts.map +1 -0
  38. package/dist/chat/kit.js +123 -0
  39. package/dist/chat/lasting.d.ts +6 -0
  40. package/dist/chat/lasting.d.ts.map +1 -0
  41. package/dist/chat/lasting.js +19 -0
  42. package/dist/chat/list.d.ts +27 -0
  43. package/dist/chat/list.d.ts.map +1 -0
  44. package/dist/chat/list.js +63 -0
  45. package/dist/chat/markdown.d.ts +20 -0
  46. package/dist/chat/markdown.d.ts.map +1 -0
  47. package/dist/chat/markdown.js +188 -0
  48. package/dist/chat/message.d.ts +6 -0
  49. package/dist/chat/message.d.ts.map +1 -0
  50. package/dist/chat/message.js +192 -0
  51. package/dist/chat/messages.d.ts +22 -0
  52. package/dist/chat/messages.d.ts.map +1 -0
  53. package/dist/chat/messages.js +155 -0
  54. package/dist/chat/name.d.ts +6 -0
  55. package/dist/chat/name.d.ts.map +1 -0
  56. package/dist/chat/name.js +37 -0
  57. package/dist/chat/opened.d.ts +11 -0
  58. package/dist/chat/opened.d.ts.map +1 -0
  59. package/dist/chat/opened.js +17 -0
  60. package/dist/chat/overlay.d.ts +31 -0
  61. package/dist/chat/overlay.d.ts.map +1 -0
  62. package/dist/chat/overlay.js +97 -0
  63. package/dist/chat/parts.d.ts +57 -0
  64. package/dist/chat/parts.d.ts.map +1 -0
  65. package/dist/chat/parts.js +119 -0
  66. package/dist/chat/process.d.ts +20 -0
  67. package/dist/chat/process.d.ts.map +1 -0
  68. package/dist/chat/process.js +32 -0
  69. package/dist/chat/provider.d.ts +63 -0
  70. package/dist/chat/provider.d.ts.map +1 -0
  71. package/dist/chat/provider.js +629 -0
  72. package/dist/chat/question.d.ts +9 -0
  73. package/dist/chat/question.d.ts.map +1 -0
  74. package/dist/chat/question.js +80 -0
  75. package/dist/chat/questions.d.ts +31 -0
  76. package/dist/chat/questions.d.ts.map +1 -0
  77. package/dist/chat/questions.js +111 -0
  78. package/dist/chat/read.d.ts +5 -0
  79. package/dist/chat/read.d.ts.map +1 -0
  80. package/dist/chat/read.js +13 -0
  81. package/dist/chat/ready.d.ts +34 -0
  82. package/dist/chat/ready.d.ts.map +1 -0
  83. package/dist/chat/ready.js +91 -0
  84. package/dist/chat/style.d.ts +4 -0
  85. package/dist/chat/style.d.ts.map +1 -0
  86. package/dist/chat/style.js +302 -0
  87. package/dist/chat.d.ts +14 -0
  88. package/dist/chat.d.ts.map +1 -0
  89. package/dist/chat.js +8 -0
  90. package/dist/client.d.ts +28 -14
  91. package/dist/client.d.ts.map +1 -1
  92. package/dist/client.js +71 -26
  93. package/dist/collection.d.ts +21 -12
  94. package/dist/collection.d.ts.map +1 -1
  95. package/dist/collection.js +271 -23
  96. package/dist/connection.d.ts +50 -23
  97. package/dist/connection.d.ts.map +1 -1
  98. package/dist/connection.js +2074 -735
  99. package/dist/conversation.d.ts +41 -14
  100. package/dist/conversation.d.ts.map +1 -1
  101. package/dist/conversation.js +546 -214
  102. package/dist/durability.d.ts +7 -1
  103. package/dist/durability.d.ts.map +1 -1
  104. package/dist/entry.d.ts +17 -0
  105. package/dist/entry.d.ts.map +1 -0
  106. package/dist/entry.js +156 -0
  107. package/dist/expectation.d.ts +21 -0
  108. package/dist/expectation.d.ts.map +1 -0
  109. package/dist/expectation.js +70 -0
  110. package/dist/failure.d.ts +13 -0
  111. package/dist/failure.d.ts.map +1 -0
  112. package/dist/failure.js +19 -0
  113. package/dist/fence.d.ts +6 -0
  114. package/dist/fence.d.ts.map +1 -0
  115. package/dist/fence.js +10 -0
  116. package/dist/host.d.ts +24 -2
  117. package/dist/host.d.ts.map +1 -1
  118. package/dist/host.js +126 -45
  119. package/dist/identity.d.ts +5 -0
  120. package/dist/identity.d.ts.map +1 -1
  121. package/dist/identity.js +19 -1
  122. package/dist/index.d.ts +16 -8
  123. package/dist/index.d.ts.map +1 -1
  124. package/dist/index.js +5 -0
  125. package/dist/memory.d.ts +17 -7
  126. package/dist/memory.d.ts.map +1 -1
  127. package/dist/memory.js +159 -46
  128. package/dist/model.d.ts +17 -14
  129. package/dist/model.d.ts.map +1 -1
  130. package/dist/model.js +16 -4
  131. package/dist/observation.d.ts +6 -1
  132. package/dist/observation.d.ts.map +1 -1
  133. package/dist/observation.js +38 -17
  134. package/dist/open.d.ts +2 -0
  135. package/dist/open.d.ts.map +1 -0
  136. package/dist/open.js +1 -0
  137. package/dist/outcome.d.ts +32 -3
  138. package/dist/outcome.d.ts.map +1 -1
  139. package/dist/outcome.js +42 -1
  140. package/dist/overlay.d.ts +16 -3
  141. package/dist/overlay.d.ts.map +1 -1
  142. package/dist/overlay.js +118 -77
  143. package/dist/paths.d.ts +0 -1
  144. package/dist/paths.d.ts.map +1 -1
  145. package/dist/paths.js +0 -16
  146. package/dist/query.d.ts +46 -6
  147. package/dist/query.d.ts.map +1 -1
  148. package/dist/query.js +85 -37
  149. package/dist/reach.d.ts +45 -0
  150. package/dist/reach.d.ts.map +1 -0
  151. package/dist/reach.js +306 -0
  152. package/dist/react.d.ts +44 -0
  153. package/dist/react.d.ts.map +1 -0
  154. package/dist/react.js +66 -0
  155. package/dist/read-side.d.ts +16 -5
  156. package/dist/read-side.d.ts.map +1 -1
  157. package/dist/read-side.js +408 -137
  158. package/dist/readiness.d.ts +53 -0
  159. package/dist/readiness.d.ts.map +1 -0
  160. package/dist/readiness.js +33 -0
  161. package/dist/recovery.d.ts +25 -0
  162. package/dist/recovery.d.ts.map +1 -0
  163. package/dist/recovery.js +197 -0
  164. package/dist/refresh.d.ts +12 -1
  165. package/dist/refresh.d.ts.map +1 -1
  166. package/dist/refresh.js +51 -36
  167. package/dist/replica.d.ts +10 -7
  168. package/dist/replica.d.ts.map +1 -1
  169. package/dist/replica.js +129 -73
  170. package/dist/room-draft.d.ts +41 -9
  171. package/dist/room-draft.d.ts.map +1 -1
  172. package/dist/room-draft.js +139 -28
  173. package/dist/rows.d.ts +11 -14
  174. package/dist/rows.d.ts.map +1 -1
  175. package/dist/rows.js +87 -50
  176. package/dist/save.d.ts +32 -3
  177. package/dist/save.d.ts.map +1 -1
  178. package/dist/save.js +465 -92
  179. package/dist/search.d.ts +37 -8
  180. package/dist/search.d.ts.map +1 -1
  181. package/dist/search.js +116 -45
  182. package/dist/seed.d.ts +16 -0
  183. package/dist/seed.d.ts.map +1 -0
  184. package/dist/seed.js +61 -0
  185. package/dist/shared.d.ts +11 -0
  186. package/dist/shared.d.ts.map +1 -0
  187. package/dist/shared.js +368 -0
  188. package/dist/status.d.ts +6 -2
  189. package/dist/status.d.ts.map +1 -1
  190. package/dist/status.js +0 -8
  191. package/dist/store.d.ts +76 -19
  192. package/dist/store.d.ts.map +1 -1
  193. package/dist/store.js +539 -200
  194. package/dist/turn.d.ts +7 -0
  195. package/dist/turn.d.ts.map +1 -0
  196. package/dist/turn.js +45 -0
  197. package/dist/wire.d.ts +162 -19
  198. package/dist/wire.d.ts.map +1 -1
  199. package/dist/wire.js +82 -61
  200. package/package.json +31 -7
  201. package/dist/attachment.d.ts +0 -3
  202. package/dist/attachment.d.ts.map +0 -1
  203. package/dist/attachment.js +0 -8
@@ -1,5 +1,8 @@
1
1
  import { conversationChangeSet } from './change-set.js';
2
+ import { runtimeError } from './failure.js';
2
3
  import { declaredFile } from './file.js';
4
+ import { changeBatchIdentity, chatDraftRevision } from './identity.js';
5
+ import { OFFLINE } from './outcome.js';
3
6
  const QUIET = {
4
7
  turn: null,
5
8
  questions: [],
@@ -7,12 +10,42 @@ const QUIET = {
7
10
  commands: [],
8
11
  options: [],
9
12
  };
13
+ export function unheldView(conversation) {
14
+ return {
15
+ conversation,
16
+ messages: [],
17
+ turn: null,
18
+ questions: [],
19
+ process: null,
20
+ commands: [],
21
+ options: [],
22
+ chatDraft: null,
23
+ };
24
+ }
25
+ export const SESSION_STARTED = 'Session started';
26
+ const NEW_CHAT = 'new:';
27
+ export function newChat(agent) {
28
+ return `${NEW_CHAT}${agent}`;
29
+ }
30
+ function creationOf(input) {
31
+ if (typeof input !== 'object' || input === null)
32
+ return null;
33
+ const { agent_id: agent, operation_id: operation } = input;
34
+ return typeof agent === 'string' && typeof operation === 'string' ? { agent, operation } : null;
35
+ }
10
36
  function startedTurn(turn, operation) {
37
+ if (operation.op === 'begin')
38
+ return { turn, started: operation.at, parts: [], context: null };
11
39
  if (operation.op === 'context') {
12
- return { turn, parts: [], context: { used: operation.used, window: operation.window } };
40
+ return {
41
+ turn,
42
+ started: null,
43
+ parts: [],
44
+ context: { used: operation.used, window: operation.window },
45
+ };
13
46
  }
14
47
  return operation.op === 'start' && operation.index === 0
15
- ? { turn, parts: [structuredClone(operation.part)], context: null }
48
+ ? { turn, started: null, parts: [structuredClone(operation.part)], context: null }
16
49
  : null;
17
50
  }
18
51
  function changedFields(part, operation) {
@@ -27,6 +60,8 @@ function changedFields(part, operation) {
27
60
  return { entries: structuredClone(operation.entries) };
28
61
  }
29
62
  function appliedTurn(held, operation) {
63
+ if (operation.op === 'begin')
64
+ return { ...held, started: operation.at };
30
65
  if (operation.op === 'context') {
31
66
  return { ...held, context: { used: operation.used, window: operation.window } };
32
67
  }
@@ -54,11 +89,15 @@ function ordered(left, right) {
54
89
  return left.identity < right.identity ? -1 : 1;
55
90
  return 0;
56
91
  }
57
- function merged(held, arriving) {
92
+ function windowOf(held, arriving, limit) {
58
93
  const byIdentity = new Map(held.map((message) => [message.identity, message]));
59
94
  for (const message of arriving)
60
95
  byIdentity.set(message.identity, message);
61
- return [...byIdentity.values()].sort(ordered);
96
+ const next = [...byIdentity.values()].sort(ordered);
97
+ return limit === undefined ? next : next.slice(Math.max(0, next.length - limit));
98
+ }
99
+ function newestOf(arriving, from) {
100
+ return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
62
101
  }
63
102
  function acceptedMessage(value) {
64
103
  if (typeof value !== 'object' || value === null)
@@ -117,84 +156,215 @@ function historyPages(result) {
117
156
  }
118
157
  return pages;
119
158
  }
120
- function turnOf(result) {
159
+ function answered(result, field) {
121
160
  if (result.kind !== 'action')
122
161
  return null;
123
162
  const answer = result.result;
124
163
  if (typeof answer !== 'object' || answer === null)
125
164
  return null;
126
- const record = answer;
127
- return typeof record.turn === 'string' ? record.turn : null;
165
+ const value = answer[field];
166
+ return typeof value === 'string' ? value : null;
128
167
  }
129
168
  function limitOf(options) {
130
169
  const { limit } = options;
131
170
  if (limit === undefined)
132
171
  return undefined;
133
172
  if (!Number.isInteger(limit) || limit < 0) {
134
- throw new Error(`conversation limit must be a non-negative integer, got ${String(limit)}`);
173
+ throw runtimeError('invalid-limit', `conversation limit must be a non-negative integer, got ${String(limit)}`);
135
174
  }
136
175
  return limit;
137
176
  }
177
+ const EXPIRED = { kind: 'rejected', code: 'expired', fields: {}, retryable: false };
178
+ function unwritten(draft) {
179
+ return draft.text === '' && draft.files.length === 0;
180
+ }
181
+ function pending(draft) {
182
+ return {
183
+ ...(draft.submitted === undefined ? {} : { submitted: draft.submitted }),
184
+ ...(draft.moves === undefined ? {} : { moves: draft.moves }),
185
+ };
186
+ }
187
+ function empty(draft) {
188
+ return unwritten(draft) && draft.submitted === undefined && draft.moves === undefined;
189
+ }
190
+ function withSubmitted(draft, submitted) {
191
+ const { submitted: _held, ...unsent } = draft;
192
+ return submitted.length === 0 ? unsent : { ...unsent, submitted };
193
+ }
194
+ function returned(draft, submission, others) {
195
+ return withSubmitted({
196
+ conversation: draft.conversation,
197
+ text: [submission.text, draft.text].filter((one) => one !== '').join('\n'),
198
+ files: [...submission.files, ...draft.files],
199
+ revision: chatDraftRevision(),
200
+ }, others);
201
+ }
138
202
  export function createConversationStore(deps) {
139
- const messages = new Map();
203
+ const stored = new Set();
140
204
  const reads = new Map();
205
+ const partials = new Set();
141
206
  const drafts = new Map();
142
207
  const sending = new Map();
143
208
  const behind = new Set();
144
209
  const gaps = new Map();
145
210
  const lives = new Map();
146
211
  const views = new Map();
212
+ const listing = new Map();
213
+ const creations = new Set();
214
+ const listWatchers = new Set();
215
+ let listKnown = false;
216
+ const agentWatchers = new Set();
217
+ let agentNames = new Map();
218
+ let retiredAgents = new Set();
219
+ let agentsRead = null;
147
220
  let replicated = new Set();
148
221
  let handedReplicated;
149
222
  function liveOf(conversation) {
150
223
  return lives.get(conversation) ?? QUIET;
151
224
  }
152
- function shownMessages(conversation, limit) {
153
- const held = messages.get(conversation) ?? [];
154
- const shown = limit === undefined ? held : held.slice(Math.max(0, held.length - limit));
155
- return shown.map(({ identity, turn, text, message }) => ({
156
- identity,
157
- turn,
158
- text,
159
- message: structuredClone(message),
160
- }));
161
- }
162
- function viewOf(conversation, limit, shown = shownMessages(conversation, limit)) {
225
+ function viewOf(conversation, held) {
163
226
  const draft = drafts.get(conversation);
164
227
  const facts = liveOf(conversation);
165
228
  return {
166
229
  conversation,
167
- messages: shown,
230
+ messages: held.map(({ identity, turn, text, message }) => ({
231
+ identity,
232
+ turn,
233
+ text,
234
+ message,
235
+ })),
168
236
  turn: structuredClone(facts.turn),
169
237
  questions: structuredClone(facts.questions),
170
238
  process: facts.process === null ? null : { ...facts.process },
171
239
  commands: structuredClone(facts.commands),
172
240
  options: structuredClone(facts.options),
173
- chatDraft: draft === undefined || draft.turn !== null || sending.get(conversation)?.draft === draft
241
+ chatDraft: draft === undefined || unwritten(draft)
174
242
  ? null
175
- : { text: draft.text, files: [...draft.files] },
243
+ : { text: draft.text, files: [...draft.files], revision: draft.revision },
176
244
  };
177
245
  }
178
- function emit(conversation, messagesChanged = true) {
246
+ function emit(conversation) {
179
247
  if (deps.closed())
180
248
  return;
181
249
  for (const view of views.values()) {
182
- if (view.conversation !== conversation)
250
+ if (view.conversation !== conversation || view.window === null)
183
251
  continue;
184
- if (messagesChanged)
185
- view.messages = null;
186
252
  const ready = [...view.watchers].filter((watcher) => watcher.ready);
187
253
  if (ready.length === 0)
188
254
  continue;
189
- view.messages ??= shownMessages(conversation, view.limit);
190
- const value = viewOf(conversation, view.limit, view.messages);
255
+ const value = viewOf(conversation, view.window);
191
256
  for (const watcher of ready)
192
257
  watcher.notify(value);
193
258
  }
194
259
  }
260
+ function slide(conversation, arriving) {
261
+ for (const view of views.values()) {
262
+ if (view.conversation !== conversation || view.window === null)
263
+ continue;
264
+ view.window = arriving === null ? [] : windowOf(view.window, arriving, view.limit);
265
+ }
266
+ }
267
+ function changesOf(conversation, arriving) {
268
+ return arriving.map(({ identity, text }) => ({ conversation, identity, text }));
269
+ }
270
+ function stateOf(conversation) {
271
+ const facts = liveOf(conversation);
272
+ if (facts.questions.length > 0)
273
+ return 'asking';
274
+ return facts.turn === null ? 'idle' : 'working';
275
+ }
276
+ function entries() {
277
+ return [...listing.values()].map((entry) => ({
278
+ conversation: entry.conversation,
279
+ agent: { identity: entry.agent, name: agentNames.get(entry.agent) ?? null },
280
+ name: entry.name,
281
+ created: entry.created,
282
+ finished: entry.finished,
283
+ outcome: entry.outcome,
284
+ state: stateOf(entry.conversation),
285
+ started: lives.get(entry.conversation)?.turn?.started ?? null,
286
+ }));
287
+ }
288
+ function emitList() {
289
+ if (deps.closed() || listWatchers.size === 0)
290
+ return;
291
+ const value = entries();
292
+ for (const watcher of listWatchers)
293
+ watcher(value);
294
+ }
295
+ function agents() {
296
+ return [...agentNames]
297
+ .filter(([identity]) => !retiredAgents.has(identity))
298
+ .map(([identity, name]) => ({ identity, name }))
299
+ .sort((left, right) => left.name === right.name
300
+ ? left.identity.localeCompare(right.identity)
301
+ : left.name.localeCompare(right.name));
302
+ }
303
+ function emitAgents() {
304
+ if (deps.closed() || agentWatchers.size === 0)
305
+ return;
306
+ const value = agents();
307
+ for (const watcher of agentWatchers)
308
+ watcher(value);
309
+ }
310
+ function setLive(conversation, next) {
311
+ const before = stateOf(conversation);
312
+ const began = lives.get(conversation)?.turn?.started;
313
+ if (next === null)
314
+ lives.delete(conversation);
315
+ else
316
+ lives.set(conversation, next);
317
+ if (!listing.has(conversation))
318
+ return;
319
+ if (stateOf(conversation) !== before || next?.turn?.started !== began)
320
+ emitList();
321
+ }
322
+ async function readAgents() {
323
+ const declaration = deps.agents();
324
+ if (declaration === undefined)
325
+ return;
326
+ const held = await deps.documents(declaration);
327
+ if ('pending' in held)
328
+ return;
329
+ const { documents } = held;
330
+ const names = new Map();
331
+ const retired = new Set();
332
+ for (const document of documents) {
333
+ if (document.type !== declaration.type)
334
+ continue;
335
+ const identity = document.fields?.[declaration.identity];
336
+ const name = document.fields?.[declaration.name];
337
+ if (typeof identity !== 'string' || typeof name !== 'string')
338
+ continue;
339
+ names.set(identity, name);
340
+ const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
341
+ if (ended !== undefined && ended !== null)
342
+ retired.add(identity);
343
+ }
344
+ const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
345
+ if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents]))
346
+ return;
347
+ agentNames = names;
348
+ retiredAgents = retired;
349
+ if (named)
350
+ emitList();
351
+ emitAgents();
352
+ const ended = [...drafts.keys()].filter(retiredChat);
353
+ if (ended.length > 0)
354
+ await remove(ended);
355
+ }
356
+ function retiredChat(conversation) {
357
+ return (conversation.startsWith(NEW_CHAT) && retiredAgents.has(conversation.slice(NEW_CHAT.length)));
358
+ }
359
+ function agentsReady() {
360
+ agentsRead ??= readAgents().catch(() => {
361
+ agentsRead = null;
362
+ });
363
+ return agentsRead;
364
+ }
195
365
  function live(conversation, change) {
196
- lives.set(conversation, { ...liveOf(conversation), ...change });
197
- emit(conversation, false);
366
+ setLive(conversation, { ...liveOf(conversation), ...change });
367
+ emit(conversation);
198
368
  }
199
369
  function relayed(conversation, turn, operation) {
200
370
  const held = liveOf(conversation).turn;
@@ -206,7 +376,7 @@ export function createConversationStore(deps) {
206
376
  const held = lives.get(conversation);
207
377
  if (held?.turn == null || !turns.has(held.turn.turn))
208
378
  return false;
209
- lives.set(conversation, { ...held, turn: null });
379
+ setLive(conversation, { ...held, turn: null });
210
380
  return true;
211
381
  }
212
382
  function handReplicated() {
@@ -220,44 +390,27 @@ export function createConversationStore(deps) {
220
390
  handedReplicated = named;
221
391
  stream.replicate(held);
222
392
  }
223
- async function documented() {
224
- const declaration = deps.cards();
225
- if (declaration === undefined)
226
- return [];
227
- const { documents } = await deps.documents(declaration.locator);
228
- return documents.filter((document) => typeof document.type === 'string');
229
- }
230
- function carded(documents) {
231
- const declaration = deps.cards();
232
- if (declaration === undefined)
233
- return [];
234
- const cards = [];
235
- for (const card of documents) {
236
- if (card.type !== declaration.type)
237
- continue;
238
- const identity = card.fields[declaration.identity];
239
- if (typeof identity === 'string')
240
- cards.push({ conversation: identity, card });
241
- }
242
- return cards;
243
- }
244
393
  async function remove(leaving) {
245
394
  await deps.durably(async (local) => {
246
- await local.dropConversations(leaving);
247
395
  const removed = [];
248
- const changed = [];
249
396
  for (const conversation of leaving) {
250
- for (const message of messages.get(conversation) ?? []) {
251
- removed.push({ conversation, identity: message.identity });
397
+ for (const identity of await local.messageIdentities(conversation)) {
398
+ removed.push({ conversation, identity, text: null });
252
399
  }
253
- if (messages.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
400
+ }
401
+ await local.dropConversations(leaving);
402
+ const changed = [];
403
+ for (const conversation of leaving) {
404
+ if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
254
405
  changed.push(conversation);
255
406
  }
256
- messages.delete(conversation);
407
+ stored.delete(conversation);
257
408
  reads.delete(conversation);
409
+ partials.delete(conversation);
258
410
  drafts.delete(conversation);
259
411
  behind.delete(conversation);
260
- lives.delete(conversation);
412
+ setLive(conversation, null);
413
+ slide(conversation, null);
261
414
  }
262
415
  if (changed.length > 0)
263
416
  deps.publish(conversationChangeSet(removed, changed));
@@ -275,30 +428,25 @@ export function createConversationStore(deps) {
275
428
  const advances = read !== undefined &&
276
429
  !behind.has(conversation) &&
277
430
  (read === null || message.position > read);
278
- const sealed = drafts.get(conversation)?.turn === message.turn;
431
+ await sealed(local, conversation, new Set([message.turn]));
279
432
  await local.keepMessages({
280
433
  conversation,
281
434
  messages: [message],
282
- ...(advances ? { read: message.position } : {}),
283
- sealed,
435
+ ...(advances ? { read: { at: message.position, partial: false } } : {}),
284
436
  });
285
- messages.set(conversation, merged(messages.get(conversation) ?? [], [message]));
437
+ stored.add(conversation);
286
438
  if (advances)
287
439
  reads.set(conversation, message.position);
288
- if (sealed)
289
- drafts.delete(conversation);
290
440
  sealTurns(conversation, new Set([message.turn]));
291
- deps.publish(conversationChangeSet([{ conversation, identity: message.identity }], [conversation]));
441
+ slide(conversation, [message]);
442
+ deps.publish(conversationChangeSet(changesOf(conversation, [message]), [conversation]));
292
443
  });
293
444
  }
294
445
  async function readHistories(conversations) {
295
446
  const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
296
- const starts = new Map(conversations.map((one) => [one, reads.get(one)]));
297
- const arriving = new Map();
298
- const complete = new Set();
299
447
  let asking = conversations.map((conversation) => ({
300
448
  conversation,
301
- after: starts.get(conversation) ?? undefined,
449
+ after: reads.get(conversation) ?? undefined,
302
450
  }));
303
451
  while (asking.length > 0) {
304
452
  const result = await deps.call('conversation.history', {
@@ -310,124 +458,244 @@ export function createConversationStore(deps) {
310
458
  const pages = historyPages(result);
311
459
  if (pages === null)
312
460
  return result;
313
- const asked = new Set(asking.map(({ conversation }) => conversation));
461
+ const asked = new Map(asking.map(({ conversation, after }) => [conversation, after]));
314
462
  const following = [];
315
463
  for (const page of pages) {
316
464
  if (!asked.has(page.conversation))
317
465
  continue;
318
- arriving.set(page.conversation, [
319
- ...(arriving.get(page.conversation) ?? []),
320
- ...page.messages,
321
- ]);
322
- if (page.next === undefined)
323
- complete.add(page.conversation);
324
- else
466
+ await keepPage(page.conversation, page.messages, asked.get(page.conversation) ?? null, page.next, generations.get(page.conversation) ?? 0);
467
+ if (page.next !== undefined) {
325
468
  following.push({ conversation: page.conversation, after: page.next });
469
+ }
326
470
  }
327
471
  asking = following;
328
472
  }
329
- for (const [conversation, held] of arriving) {
330
- if (!complete.has(conversation))
331
- continue;
332
- await keepHistory(conversation, held, starts.get(conversation), generations.get(conversation) ?? 0);
333
- }
334
473
  return null;
335
474
  }
336
- function keepHistory(conversation, arriving, from, generation) {
475
+ function keepPage(conversation, arriving, from, next, generation) {
337
476
  return deps.durably(async (local) => {
338
477
  if (!replicated.has(conversation))
339
478
  return;
340
- const newest = arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from ?? null);
341
- const turn = drafts.get(conversation)?.turn;
342
- const sealed = arriving.some((message) => message.turn === turn);
343
- await local.keepMessages({ conversation, messages: arriving, read: newest, sealed });
344
- messages.set(conversation, merged(messages.get(conversation) ?? [], arriving));
345
- reads.set(conversation, newest);
346
- if (sealed)
347
- drafts.delete(conversation);
479
+ const whole = next === undefined;
480
+ const cursor = next ?? newestOf(arriving, from);
481
+ const partial = !whole && (partials.has(conversation) || !reads.has(conversation));
482
+ const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
483
+ await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
484
+ stored.add(conversation);
485
+ reads.set(conversation, cursor);
486
+ if (partial)
487
+ partials.add(conversation);
488
+ else
489
+ partials.delete(conversation);
348
490
  sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
349
- if ((gaps.get(conversation) ?? 0) === generation)
491
+ if (whole && (gaps.get(conversation) ?? 0) === generation)
350
492
  behind.delete(conversation);
351
- if (arriving.length > 0 || sealed) {
352
- deps.publish(conversationChangeSet(arriving.map((message) => ({ conversation, identity: message.identity })), [conversation]));
493
+ slide(conversation, arriving);
494
+ if (arriving.length > 0 || sealing || whole) {
495
+ deps.publish(conversationChangeSet(changesOf(conversation, arriving), [conversation]));
353
496
  }
354
497
  });
355
498
  }
499
+ function heard(event) {
500
+ const { conversation } = event;
501
+ if (event.kind === 'gap') {
502
+ if (replicated.has(conversation))
503
+ gap(conversation);
504
+ const held = lives.get(conversation);
505
+ if (held === undefined)
506
+ return;
507
+ setLive(conversation, { ...QUIET, turn: held.turn });
508
+ emit(conversation);
509
+ return;
510
+ }
511
+ if (event.kind === 'turn') {
512
+ relayed(conversation, event.turn, event.operation);
513
+ return;
514
+ }
515
+ if (event.kind === 'questions') {
516
+ live(conversation, { questions: structuredClone(event.questions) });
517
+ return;
518
+ }
519
+ if (event.kind === 'process') {
520
+ live(conversation, { process: { running: event.running } });
521
+ return;
522
+ }
523
+ if (event.kind === 'commands') {
524
+ live(conversation, { commands: structuredClone(event.commands) });
525
+ return;
526
+ }
527
+ if (event.kind === 'options') {
528
+ live(conversation, { options: structuredClone(event.options) });
529
+ return;
530
+ }
531
+ const arriving = acceptedMessage(event.message);
532
+ if (arriving === null)
533
+ return;
534
+ if (replicated.has(conversation)) {
535
+ void keep(conversation, arriving).catch(() => { });
536
+ return;
537
+ }
538
+ if (sealTurns(conversation, new Set([arriving.turn])))
539
+ emit(conversation);
540
+ }
541
+ async function kept(local, conversation, following) {
542
+ if (following === null || empty(following)) {
543
+ await local.dropChatDraft(conversation);
544
+ drafts.delete(conversation);
545
+ }
546
+ else {
547
+ await local.keepChatDrafts([following]);
548
+ drafts.set(conversation, following);
549
+ }
550
+ }
551
+ async function sealed(local, conversation, turns) {
552
+ const held = drafts.get(conversation);
553
+ const submitted = held?.submitted ?? [];
554
+ const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
555
+ if (held === undefined || left.length === submitted.length)
556
+ return false;
557
+ await kept(local, conversation, withSubmitted(held, left));
558
+ return true;
559
+ }
560
+ function moved(agent, operation, to) {
561
+ const from = newChat(agent);
562
+ return deps.durably(async (local) => {
563
+ const held = drafts.get(from);
564
+ const earlier = (held?.moves ?? []).filter((one) => !deps.expired(one));
565
+ if (earlier.includes(operation))
566
+ return;
567
+ const blank = {
568
+ conversation: from,
569
+ text: '',
570
+ files: [],
571
+ revision: chatDraftRevision(),
572
+ };
573
+ const { moves: _answered, ...unsent } = held ?? blank;
574
+ const taken = drafts.has(to) || empty(unsent) ? [] : [{ ...unsent, conversation: to }];
575
+ const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
576
+ await local.keepChatDrafts([left, ...taken]);
577
+ drafts.set(from, left);
578
+ for (const one of taken)
579
+ drafts.set(to, one);
580
+ deps.publish(conversationChangeSet([], [from, to]));
581
+ });
582
+ }
356
583
  function withDraft(conversation, next) {
357
584
  return deps.durably(async (local) => {
358
- const following = next(drafts.get(conversation));
585
+ const following = await next(drafts.get(conversation), local);
359
586
  if (following === undefined)
360
587
  return;
361
- if (following === null) {
362
- await local.dropChatDraft(conversation);
363
- drafts.delete(conversation);
364
- }
365
- else {
366
- await local.keepChatDraft(following);
367
- drafts.set(conversation, following);
368
- }
588
+ await kept(local, conversation, following);
369
589
  deps.publish(conversationChangeSet([], [conversation]));
370
590
  });
371
591
  }
592
+ async function submitted(conversation, operation, draft) {
593
+ let submission;
594
+ let earlier = false;
595
+ await withDraft(conversation, (current) => {
596
+ submission = current?.submitted?.find((one) => one.operation === operation);
597
+ earlier = submission !== undefined;
598
+ if (submission !== undefined)
599
+ return undefined;
600
+ const said = draft ?? current;
601
+ submission = {
602
+ operation,
603
+ text: said?.text ?? '',
604
+ files: [...(said?.files ?? [])],
605
+ turn: null,
606
+ };
607
+ return {
608
+ conversation,
609
+ ...(draft === undefined || current === undefined
610
+ ? { text: '', files: [], revision: chatDraftRevision() }
611
+ : { text: current.text, files: current.files, revision: current.revision }),
612
+ submitted: [...(current?.submitted ?? []), submission],
613
+ };
614
+ });
615
+ return { submission: submission, earlier };
616
+ }
617
+ function forgotten(conversation, submission) {
618
+ return withDraft(conversation, (current) => {
619
+ const held = current?.submitted ?? [];
620
+ if (current === undefined || !held.includes(submission))
621
+ return undefined;
622
+ return withSubmitted(current, held.filter((one) => one !== submission));
623
+ });
624
+ }
625
+ async function dispatched(conversation, submission) {
626
+ const dispatch = {};
627
+ sending.set(submission.operation, dispatch);
628
+ try {
629
+ const files = await Promise.all(submission.files.map(async (file) => ({ ...(await declaredFile(file)), blob: file.blob })));
630
+ const result = await invoke('conversation.send', conversation, {
631
+ text: submission.text,
632
+ files,
633
+ operation_id: submission.operation,
634
+ });
635
+ if (result.kind === 'revoked-authorization')
636
+ return result;
637
+ if (result.kind !== 'action' && result.retryable) {
638
+ deps.unanswered();
639
+ return result;
640
+ }
641
+ const turn = answered(result, 'turn');
642
+ await withDraft(conversation, async (current, local) => {
643
+ const held = current?.submitted?.find((one) => one.operation === submission.operation);
644
+ if (current === undefined || held === undefined || held.turn !== null)
645
+ return undefined;
646
+ const others = current.submitted.filter((one) => one !== held);
647
+ if (result.kind !== 'action')
648
+ return returned(current, held, others);
649
+ const arrived = turn !== null && (await local.turnArrived(conversation, turn));
650
+ return withSubmitted(current, turn === null || arrived
651
+ ? others
652
+ : current.submitted.map((one) => (one === held ? { ...held, turn } : one)));
653
+ });
654
+ return result;
655
+ }
656
+ finally {
657
+ if (sending.get(submission.operation) === dispatch)
658
+ sending.delete(submission.operation);
659
+ }
660
+ }
372
661
  function invoke(name, conversation, extra = {}) {
373
662
  return deps.call(name, { conversation, ...extra });
374
663
  }
375
664
  const surface = {
376
665
  async list() {
377
- const declaration = deps.cards();
378
- if (declaration === undefined)
379
- return [];
380
- const documents = await documented();
381
- const entries = carded(documents).map(({ conversation, card }) => ({
382
- conversation,
383
- card,
384
- agent: documents.find((document) => document.backlinks.some((backlink) => backlink.path === card.path && backlink.field === declaration.agent)) ?? null,
385
- }));
386
- entries.sort((left, right) => (left.conversation < right.conversation ? -1 : 1));
387
- return entries;
666
+ await agentsReady();
667
+ return entries();
388
668
  },
389
669
  async read(conversation, options = {}) {
390
670
  const limit = limitOf(options);
391
- return deps.durably(() => Promise.resolve(viewOf(conversation, limit)));
671
+ return deps.durably(async (local) => structuredClone(viewOf(conversation, await local.messageWindow(conversation, limit))));
392
672
  },
393
- create(input) {
394
- return deps.call('conversation.create', input);
395
- },
396
- async send(conversation) {
397
- const draft = drafts.get(conversation);
398
- const pending = draft === undefined ? undefined : { draft };
399
- if (pending !== undefined) {
400
- sending.set(conversation, pending);
401
- deps.publish(conversationChangeSet([], [conversation]));
402
- }
403
- try {
404
- const files = await Promise.all((draft?.files ?? []).map(async (file) => ({
405
- ...(await declaredFile(file)),
406
- blob: file.blob,
407
- })));
408
- const result = await invoke('conversation.send', conversation, {
409
- text: draft?.text ?? '',
410
- files,
411
- });
412
- const turn = turnOf(result);
413
- if (turn === null || draft === undefined)
414
- return result;
415
- await withDraft(conversation, (current) => {
416
- if (current !== draft)
417
- return undefined;
418
- const sealed = (messages.get(conversation) ?? []).some((message) => message.turn === turn);
419
- return sealed ? null : { ...draft, turn };
420
- });
673
+ async create(input) {
674
+ const result = await deps.call('conversation.create', input);
675
+ const conversation = answered(result, 'conversation');
676
+ if (conversation === null)
421
677
  return result;
678
+ if (!listing.has(conversation))
679
+ creations.add(conversation);
680
+ const creation = creationOf(input);
681
+ if (creation !== null)
682
+ await moved(creation.agent, creation.operation, conversation);
683
+ return result;
684
+ },
685
+ async send(conversation, operation = changeBatchIdentity(), draft) {
686
+ if (deps.online()) {
687
+ const { submission, earlier } = await submitted(conversation, operation, draft);
688
+ if (!earlier || !deps.expired(operation))
689
+ return dispatched(conversation, submission);
690
+ await forgotten(conversation, submission);
691
+ return EXPIRED;
422
692
  }
423
- finally {
424
- if (pending !== undefined && sending.get(conversation) === pending) {
425
- sending.delete(conversation);
426
- if (drafts.get(conversation) === draft) {
427
- deps.publish(conversationChangeSet([], [conversation]));
428
- }
429
- }
693
+ if (draft !== undefined) {
694
+ await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
695
+ ? undefined
696
+ : returned(current ?? { conversation, text: '', files: [], revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
430
697
  }
698
+ return OFFLINE;
431
699
  },
432
700
  answer(conversation, answer) {
433
701
  return invoke('conversation.answer', conversation, { answer });
@@ -442,7 +710,12 @@ export function createConversationStore(deps) {
442
710
  const result = await invoke('conversation.cancel', conversation, { turn });
443
711
  if (result.kind !== 'action')
444
712
  return result;
445
- await withDraft(conversation, (current) => current === undefined || current.turn !== turn ? undefined : { ...current, turn: null });
713
+ await withDraft(conversation, (current) => {
714
+ const held = current?.submitted?.find((one) => one.turn === turn);
715
+ if (current === undefined || held === undefined)
716
+ return undefined;
717
+ return returned(current, held, current.submitted.filter((one) => one !== held));
718
+ });
446
719
  return result;
447
720
  },
448
721
  delete(conversation) {
@@ -450,40 +723,54 @@ export function createConversationStore(deps) {
450
723
  },
451
724
  };
452
725
  const chatDraft = {
453
- write(conversation, draft) {
454
- return withDraft(conversation, () => ({
726
+ write(conversation, draft, revision = chatDraftRevision()) {
727
+ return withDraft(conversation, (current) => ({
455
728
  conversation,
456
729
  text: draft.text,
457
730
  files: [...draft.files],
458
- turn: null,
731
+ revision,
732
+ ...(current === undefined ? {} : pending(current)),
459
733
  }));
460
734
  },
461
735
  discard(conversation) {
462
- return withDraft(conversation, (current) => (current === undefined ? undefined : null));
736
+ return withDraft(conversation, (current) => current === undefined
737
+ ? undefined
738
+ : {
739
+ conversation,
740
+ text: '',
741
+ files: [],
742
+ revision: chatDraftRevision(),
743
+ ...pending(current),
744
+ });
463
745
  },
464
746
  };
465
747
  return {
466
748
  surface,
467
749
  chatDraft,
468
- hydrate(held, stored) {
469
- messages.clear();
750
+ hydrate(held, kept) {
751
+ stored.clear();
470
752
  reads.clear();
471
- for (const view of views.values())
472
- view.messages = null;
753
+ partials.clear();
473
754
  for (const entry of held) {
474
- messages.set(entry.conversation, [...entry.messages].sort(ordered));
755
+ stored.add(entry.conversation);
475
756
  if (entry.read !== undefined)
476
757
  reads.set(entry.conversation, entry.read);
758
+ if (entry.partial)
759
+ partials.add(entry.conversation);
477
760
  }
478
761
  drafts.clear();
479
- for (const record of stored)
762
+ for (const record of kept)
480
763
  drafts.set(record.conversation, record);
481
764
  },
482
765
  async reconcile() {
483
- const named = new Set(carded(await documented()).map((entry) => entry.conversation));
766
+ if (!listKnown)
767
+ return;
768
+ const named = new Set(listing.keys());
484
769
  const leaving = [
485
- ...new Set([...replicated, ...messages.keys(), ...reads.keys(), ...drafts.keys()]),
486
- ].filter((conversation) => !named.has(conversation));
770
+ ...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
771
+ ].filter((conversation) => !named.has(conversation) &&
772
+ !creations.has(conversation) &&
773
+ (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
487
774
  for (const conversation of named) {
488
775
  if (!replicated.has(conversation))
489
776
  behind.add(conversation);
@@ -503,13 +790,44 @@ export function createConversationStore(deps) {
503
790
  if (behind.size > 0)
504
791
  progress.complete = false;
505
792
  },
793
+ async resend(progress) {
794
+ for (const [conversation, held] of [...drafts]) {
795
+ for (const submission of held.submitted ?? []) {
796
+ if (submission.turn !== null || sending.has(submission.operation))
797
+ continue;
798
+ if (deps.closed())
799
+ return;
800
+ if (deps.expired(submission.operation)) {
801
+ await forgotten(conversation, submission);
802
+ continue;
803
+ }
804
+ const result = await dispatched(conversation, submission);
805
+ if (result.kind === 'revoked-authorization')
806
+ return;
807
+ if (result.kind === 'transport-uncertainty') {
808
+ progress.reachable = false;
809
+ return;
810
+ }
811
+ if (result.kind !== 'action' && result.retryable) {
812
+ progress.complete = false;
813
+ return;
814
+ }
815
+ }
816
+ }
817
+ },
506
818
  current() {
507
819
  return behind.size === 0;
508
820
  },
821
+ known() {
822
+ return listKnown;
823
+ },
509
824
  reconnected() {
510
825
  for (const conversation of replicated)
511
826
  gap(conversation);
512
827
  },
828
+ reopened() {
829
+ sending.clear();
830
+ },
513
831
  watch(conversation, options, deliver) {
514
832
  const limit = limitOf(options);
515
833
  const key = JSON.stringify([conversation, limit ?? null]);
@@ -517,18 +835,20 @@ export function createConversationStore(deps) {
517
835
  conversation,
518
836
  limit,
519
837
  watchers: new Set(),
520
- messages: null,
838
+ window: null,
521
839
  };
522
840
  views.set(key, view);
523
841
  const watcher = { notify: deliver, ready: false };
524
842
  view.watchers.add(watcher);
525
843
  void deps
526
- .durably(() => Promise.resolve())
844
+ .durably(async (local) => {
845
+ view.window ??= await local.messageWindow(conversation, limit);
846
+ })
527
847
  .then(() => {
528
848
  if (deps.closed() || !view.watchers.has(watcher))
529
849
  return;
530
850
  watcher.ready = true;
531
- watcher.notify(viewOf(conversation, limit));
851
+ watcher.notify(viewOf(conversation, view.window ?? []));
532
852
  })
533
853
  .catch(() => { });
534
854
  return () => {
@@ -538,64 +858,76 @@ export function createConversationStore(deps) {
538
858
  views.delete(key);
539
859
  };
540
860
  },
861
+ watchList(deliver) {
862
+ listWatchers.add(deliver);
863
+ void agentsReady().then(() => {
864
+ if (!deps.closed() && listWatchers.has(deliver))
865
+ deliver(entries());
866
+ });
867
+ return () => {
868
+ listWatchers.delete(deliver);
869
+ };
870
+ },
871
+ watchAgents(deliver) {
872
+ agentWatchers.add(deliver);
873
+ void agentsReady().then(() => {
874
+ if (!deps.closed() && agentWatchers.has(deliver))
875
+ deliver(agents());
876
+ });
877
+ return () => {
878
+ agentWatchers.delete(deliver);
879
+ };
880
+ },
541
881
  changed(set) {
542
882
  for (const conversation of set.conversations)
543
883
  emit(conversation);
884
+ const declaration = deps.agents();
885
+ if (declaration !== undefined &&
886
+ set.documents.some((change) => change.locator === declaration.locator)) {
887
+ agentsRead = readAgents().catch(() => {
888
+ agentsRead = null;
889
+ });
890
+ }
544
891
  },
545
892
  receive(event) {
546
893
  if (deps.closed())
547
- return;
548
- const { conversation } = event;
549
- if (event.kind === 'gap') {
550
- if (replicated.has(conversation))
551
- gap(conversation);
552
- const held = lives.get(conversation);
553
- if (held === undefined)
554
- return;
555
- lives.set(conversation, { ...QUIET, turn: held.turn });
556
- emit(conversation, false);
557
- return;
558
- }
559
- if (event.kind === 'turn') {
560
- relayed(conversation, event.turn, event.operation);
561
- return;
562
- }
563
- if (event.kind === 'questions') {
564
- live(conversation, { questions: structuredClone(event.questions) });
565
- return;
566
- }
567
- if (event.kind === 'process') {
568
- live(conversation, { process: { running: event.running } });
569
- return;
570
- }
571
- if (event.kind === 'commands') {
572
- live(conversation, { commands: structuredClone(event.commands) });
573
- return;
574
- }
575
- if (event.kind === 'options') {
576
- live(conversation, { options: structuredClone(event.options) });
577
- return;
578
- }
579
- const arriving = acceptedMessage(event.message);
580
- if (arriving === null)
581
- return;
582
- if (replicated.has(conversation)) {
583
- void keep(conversation, arriving).catch(() => { });
584
- return;
894
+ return false;
895
+ if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
896
+ if (event.kind === 'list') {
897
+ listing.clear();
898
+ for (const entry of event.entries) {
899
+ listing.set(entry.conversation, { ...entry });
900
+ creations.delete(entry.conversation);
901
+ }
902
+ listKnown = true;
903
+ }
904
+ else if (event.kind === 'listed') {
905
+ listing.set(event.entry.conversation, { ...event.entry });
906
+ creations.delete(event.entry.conversation);
907
+ }
908
+ else {
909
+ listing.delete(event.conversation);
910
+ }
911
+ emitList();
912
+ return (event.kind !== 'list' &&
913
+ listKnown &&
914
+ (listing.size !== replicated.size ||
915
+ [...listing.keys()].some((conversation) => !replicated.has(conversation))));
585
916
  }
586
- if (sealTurns(conversation, new Set([arriving.turn])))
587
- emit(conversation);
917
+ heard(event);
918
+ return false;
588
919
  },
589
- messages() {
590
- return [...messages].flatMap(([conversation, held]) => held.map(({ identity, text }) => ({ conversation, identity, text })));
920
+ searchable() {
921
+ return [...(listKnown ? listing.keys() : stored)].sort();
591
922
  },
592
- message(conversation, identity) {
593
- const held = messages.get(conversation)?.find((message) => message.identity === identity);
594
- return held === undefined ? null : { conversation, identity, text: held.text };
923
+ acquired(conversation) {
924
+ return reads.has(conversation) && !partials.has(conversation);
595
925
  },
596
926
  close() {
597
927
  views.clear();
598
928
  lives.clear();
929
+ listWatchers.clear();
930
+ agentWatchers.clear();
599
931
  },
600
932
  };
601
933
  }