@agentshouse/mdruntime 0.1.0-c97456d63baba → 0.1.0-c99ee6ff87175

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