@agentshouse/mdruntime 0.1.0-cb0eb0d87ff49 → 0.1.0-cb1f56e16291b

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