@agentshouse/mdruntime 0.1.0-ca4dc698bd0c4 → 0.1.0-ca58768ab31fe

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 +146 -79
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/attached-model.d.ts +5 -1
  5. package/dist/attached-model.d.ts.map +1 -1
  6. package/dist/attached-model.js +38 -4
  7. package/dist/binary.d.ts +0 -5
  8. package/dist/binary.d.ts.map +1 -1
  9. package/dist/binary.js +1 -1
  10. package/dist/bootstrap.d.ts +27 -11
  11. package/dist/bootstrap.d.ts.map +1 -1
  12. package/dist/bootstrap.js +181 -99
  13. package/dist/chain.d.ts +12 -0
  14. package/dist/chain.d.ts.map +1 -0
  15. package/dist/chain.js +64 -0
  16. package/dist/change-set.d.ts +9 -16
  17. package/dist/change-set.d.ts.map +1 -1
  18. package/dist/change-set.js +11 -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 +24 -11
  23. package/dist/client.d.ts.map +1 -1
  24. package/dist/client.js +98 -11
  25. package/dist/collection.d.ts +11 -21
  26. package/dist/collection.d.ts.map +1 -1
  27. package/dist/collection.js +39 -291
  28. package/dist/compile.d.ts +11 -0
  29. package/dist/compile.d.ts.map +1 -0
  30. package/dist/compile.js +586 -0
  31. package/dist/connection.d.ts +33 -17
  32. package/dist/connection.d.ts.map +1 -1
  33. package/dist/connection.js +3082 -1404
  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 +31 -15
  38. package/dist/conversation.d.ts.map +1 -1
  39. package/dist/conversation.js +524 -223
  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 +7 -4
  44. package/dist/durability.d.ts.map +1 -1
  45. package/dist/durability.js +5 -0
  46. package/dist/entry.d.ts +5 -2
  47. package/dist/entry.d.ts.map +1 -1
  48. package/dist/entry.js +30 -27
  49. package/dist/file.d.ts +2 -19
  50. package/dist/file.d.ts.map +1 -1
  51. package/dist/file.js +7 -122
  52. package/dist/host.d.ts +6 -8
  53. package/dist/host.d.ts.map +1 -1
  54. package/dist/host.js +98 -24
  55. package/dist/identity.d.ts +0 -2
  56. package/dist/identity.d.ts.map +1 -1
  57. package/dist/identity.js +0 -6
  58. package/dist/index.d.ts +20 -13
  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 +17 -7
  71. package/dist/memory.d.ts.map +1 -1
  72. package/dist/memory.js +209 -103
  73. package/dist/messages.d.ts +28 -0
  74. package/dist/messages.d.ts.map +1 -0
  75. package/dist/messages.js +89 -0
  76. package/dist/model.d.ts +10 -5
  77. package/dist/model.d.ts.map +1 -1
  78. package/dist/model.js +8 -14
  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/query.d.ts +17 -185
  98. package/dist/query.d.ts.map +1 -1
  99. package/dist/query.js +3 -464
  100. package/dist/reach.d.ts +16 -8
  101. package/dist/reach.d.ts.map +1 -1
  102. package/dist/reach.js +221 -90
  103. package/dist/react.d.ts +6 -4
  104. package/dist/react.d.ts.map +1 -1
  105. package/dist/react.js +7 -17
  106. package/dist/readiness.d.ts +14 -17
  107. package/dist/readiness.d.ts.map +1 -1
  108. package/dist/readiness.js +2 -8
  109. package/dist/recovery.d.ts +3 -2
  110. package/dist/recovery.d.ts.map +1 -1
  111. package/dist/recovery.js +34 -7
  112. package/dist/refresh.d.ts +17 -14
  113. package/dist/refresh.d.ts.map +1 -1
  114. package/dist/refresh.js +136 -52
  115. package/dist/replica.d.ts +56 -33
  116. package/dist/replica.d.ts.map +1 -1
  117. package/dist/replica.js +298 -182
  118. package/dist/room-draft.d.ts +37 -6
  119. package/dist/room-draft.d.ts.map +1 -1
  120. package/dist/room-draft.js +161 -74
  121. package/dist/rows.d.ts +22 -29
  122. package/dist/rows.d.ts.map +1 -1
  123. package/dist/rows.js +58 -112
  124. package/dist/save.d.ts +53 -14
  125. package/dist/save.d.ts.map +1 -1
  126. package/dist/save.js +529 -185
  127. package/dist/search.d.ts +15 -77
  128. package/dist/search.d.ts.map +1 -1
  129. package/dist/search.js +10 -184
  130. package/dist/seed.d.ts +16 -0
  131. package/dist/seed.d.ts.map +1 -0
  132. package/dist/seed.js +75 -0
  133. package/dist/shared.d.ts +7 -3
  134. package/dist/shared.d.ts.map +1 -1
  135. package/dist/shared.js +119 -66
  136. package/dist/status.d.ts +4 -3
  137. package/dist/status.d.ts.map +1 -1
  138. package/dist/status.js +3 -1
  139. package/dist/store.d.ts +90 -98
  140. package/dist/store.d.ts.map +1 -1
  141. package/dist/store.js +303 -661
  142. package/dist/stored.d.ts +129 -0
  143. package/dist/stored.d.ts.map +1 -0
  144. package/dist/stored.js +495 -0
  145. package/dist/tables.d.ts +328 -0
  146. package/dist/tables.d.ts.map +1 -0
  147. package/dist/tables.js +587 -0
  148. package/dist/transfer.d.ts +0 -5
  149. package/dist/transfer.d.ts.map +1 -1
  150. package/dist/turn.d.ts +5 -3
  151. package/dist/turn.d.ts.map +1 -1
  152. package/dist/turn.js +51 -21
  153. package/dist/validation.d.ts +29 -0
  154. package/dist/validation.d.ts.map +1 -0
  155. package/dist/validation.js +104 -0
  156. package/dist/windows.d.ts +11 -0
  157. package/dist/windows.d.ts.map +1 -0
  158. package/dist/windows.js +16 -0
  159. package/dist/wire.d.ts +172 -33
  160. package/dist/wire.d.ts.map +1 -1
  161. package/dist/wire.js +57 -32
  162. package/package.json +18 -19
  163. package/AGENTS.md +0 -300
  164. package/dist/acquisition.d.ts +0 -14
  165. package/dist/acquisition.d.ts.map +0 -1
  166. package/dist/acquisition.js +0 -24
  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,32 @@
1
+ import { cursorOf, decodedCursor } from '@agentshouse/mdmodel';
1
2
  import { conversationChangeSet } from './change-set.js';
2
3
  import { runtimeError } from './failure.js';
3
- import { declaredFile } from './file.js';
4
+ import { refusedForSpace } from './durability.js';
4
5
  import { changeBatchIdentity, chatDraftRevision } from './identity.js';
5
6
  import { OFFLINE } from './outcome.js';
7
+ import { untilAnswered } from './recovery.js';
8
+ import { capped, FROM_START, messageOrder, precedes } from './windows.js';
6
9
  const QUIET = {
7
10
  turn: null,
8
11
  questions: [],
9
12
  process: null,
10
13
  commands: [],
11
14
  options: [],
15
+ starting: null,
16
+ subagents: [],
17
+ link: null,
18
+ deliveryUnknown: [],
12
19
  };
20
+ const FACTS = [
21
+ 'questions',
22
+ 'process',
23
+ 'commands',
24
+ 'options',
25
+ 'starting',
26
+ 'subagents',
27
+ 'link',
28
+ 'deliveryUnknown',
29
+ ];
13
30
  export function unheldView(conversation) {
14
31
  return {
15
32
  conversation,
@@ -17,12 +34,28 @@ export function unheldView(conversation) {
17
34
  turn: null,
18
35
  questions: [],
19
36
  process: null,
37
+ processEnded: null,
20
38
  commands: [],
21
39
  options: [],
40
+ starting: null,
41
+ subagents: [],
42
+ link: null,
43
+ deliveryUnknown: [],
22
44
  chatDraft: null,
23
45
  };
24
46
  }
25
47
  export const SESSION_STARTED = 'Session started';
48
+ const NEW_CHAT = 'new:';
49
+ export const HISTORY_CONVERSATIONS = 64;
50
+ export function newChat(agent) {
51
+ return `${NEW_CHAT}${agent}`;
52
+ }
53
+ function creationOf(input) {
54
+ if (typeof input !== 'object' || input === null)
55
+ return null;
56
+ const { agent_id: agent, operation_id: operation } = input;
57
+ return typeof agent === 'string' && typeof operation === 'string' ? { agent, operation } : null;
58
+ }
26
59
  function startedTurn(turn, operation) {
27
60
  if (operation.op === 'begin')
28
61
  return { turn, started: operation.at, parts: [], context: null };
@@ -72,19 +105,25 @@ function appliedTurn(held, operation) {
72
105
  parts: held.parts.map((kept, at) => (at === operation.index ? changed : kept)),
73
106
  };
74
107
  }
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
108
  function windowOf(held, arriving, limit) {
83
109
  const byIdentity = new Map(held.map((message) => [message.identity, message]));
84
110
  for (const message of arriving)
85
111
  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));
112
+ const next = [...byIdentity.values()].sort(messageOrder);
113
+ const start = Math.max(0, next.length - limit);
114
+ return { messages: next.slice(start), dropped: start > 0 };
115
+ }
116
+ function frameOf(asked, messages, more, total) {
117
+ const oldest = messages[0];
118
+ const next = !more
119
+ ? undefined
120
+ : oldest === undefined
121
+ ? (asked.after ?? FROM_START)
122
+ : cursorOf([oldest.position, oldest.identity]);
123
+ return {
124
+ ...(next === undefined ? {} : { next }),
125
+ ...(total === null ? {} : { total }),
126
+ };
88
127
  }
89
128
  function newestOf(arriving, from) {
90
129
  return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
@@ -146,40 +185,71 @@ function historyPages(result) {
146
185
  }
147
186
  return pages;
148
187
  }
149
- function turnOf(result) {
188
+ function answered(result, field) {
150
189
  if (result.kind !== 'action')
151
190
  return null;
152
191
  const answer = result.result;
153
192
  if (typeof answer !== 'object' || answer === null)
154
193
  return null;
155
- const record = answer;
156
- return typeof record.turn === 'string' ? record.turn : null;
194
+ const value = answer[field];
195
+ return typeof value === 'string' ? value : null;
157
196
  }
158
- function limitOf(options) {
159
- const { limit } = options;
160
- if (limit === undefined)
161
- return undefined;
162
- if (!Number.isInteger(limit) || limit < 0) {
197
+ function beforeOf(after) {
198
+ if (after === undefined)
199
+ return null;
200
+ const decoded = typeof after === 'string' ? decodedCursor(after) : null;
201
+ if (decoded?.length === 0)
202
+ return null;
203
+ const [position, identity] = decoded ?? [];
204
+ if (decoded?.length !== 2 || typeof position !== 'string' || typeof identity !== 'string') {
205
+ throw runtimeError('invalid-after', `conversation after must be the next an earlier view returned, got ${String(after)}`);
206
+ }
207
+ return [position, identity];
208
+ }
209
+ function askedOf(options) {
210
+ const { limit, after, total } = options;
211
+ if (limit !== undefined && (!Number.isInteger(limit) || limit < 0)) {
163
212
  throw runtimeError('invalid-limit', `conversation limit must be a non-negative integer, got ${String(limit)}`);
164
213
  }
165
- return limit;
214
+ return { limit: capped(limit), after, before: beforeOf(after), total: total === true };
166
215
  }
167
- const EXPIRED = { kind: 'rejected', code: 'expired', fields: {}, retryable: false };
216
+ function phraseOf(text) {
217
+ if (text === undefined)
218
+ return null;
219
+ if (typeof text !== 'string' || text === '') {
220
+ throw runtimeError('invalid-text', 'a conversation is found by a phrase');
221
+ }
222
+ return text;
223
+ }
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,22 +260,31 @@ 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, held) {
287
+ function viewOf(conversation, held, frame) {
209
288
  const draft = drafts.get(conversation);
210
289
  const facts = liveOf(conversation);
211
290
  return {
@@ -216,14 +295,20 @@ export function createConversationStore(deps) {
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,65 +762,71 @@ 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 ?? [];
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) {
547
797
  let submission;
548
- let earlier = false;
549
798
  await withDraft(conversation, (current) => {
550
799
  submission = current?.submitted?.find((one) => one.operation === operation);
551
- earlier = submission !== undefined;
552
800
  if (submission !== undefined)
553
801
  return undefined;
554
802
  const said = draft ?? current;
555
- submission = {
556
- operation,
557
- text: said?.text ?? '',
558
- files: [...(said?.files ?? [])],
559
- turn: null,
560
- };
803
+ submission = { operation, text: said?.text ?? '', turn: null };
561
804
  return {
562
805
  conversation,
563
806
  ...(draft === undefined || current === undefined
564
- ? { text: '', files: [], revision: chatDraftRevision() }
565
- : { text: current.text, files: current.files, revision: current.revision }),
807
+ ? { text: '', revision: chatDraftRevision() }
808
+ : { text: current.text, revision: current.revision }),
566
809
  submitted: [...(current?.submitted ?? []), submission],
567
810
  };
568
811
  });
569
- return { submission: submission, earlier };
570
- }
571
- function forgotten(conversation, submission) {
572
- return withDraft(conversation, (current) => {
573
- const held = current?.submitted ?? [];
574
- if (current === undefined || !held.includes(submission))
575
- return undefined;
576
- return withSubmitted(current, held.filter((one) => one !== submission));
577
- });
812
+ return submission;
578
813
  }
579
814
  async function dispatched(conversation, submission) {
580
- const dispatch = {};
815
+ const dispatch = { answered: false };
581
816
  sending.set(submission.operation, dispatch);
582
817
  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,
818
+ const result = await invoke('conversations/send', conversation, {
587
819
  operation_id: submission.operation,
820
+ text: submission.text,
588
821
  });
822
+ dispatch.answered = result.kind !== 'transport-uncertainty';
589
823
  if (result.kind === 'revoked-authorization')
590
824
  return result;
591
825
  if (result.kind !== 'action' && result.retryable) {
592
826
  deps.unanswered();
593
827
  return result;
594
828
  }
595
- const turn = turnOf(result);
829
+ const turn = answered(result, 'message_id');
596
830
  await withDraft(conversation, async (current, local) => {
597
831
  const held = current?.submitted?.find((one) => one.operation === submission.operation);
598
832
  if (current === undefined || held === undefined || held.turn !== null)
@@ -612,59 +846,77 @@ export function createConversationStore(deps) {
612
846
  sending.delete(submission.operation);
613
847
  }
614
848
  }
615
- function invoke(name, conversation, extra = {}) {
616
- return deps.call(name, { conversation, ...extra });
849
+ function invoke(name, conversation, extra = { operation_id: changeBatchIdentity() }) {
850
+ return deps.call(name, { conversation_id: conversation, ...extra });
617
851
  }
618
852
  const surface = {
619
- async list() {
620
- await agentsReady();
621
- return entries();
853
+ list() {
854
+ return new Promise((settle) => {
855
+ const answer = (listed) => {
856
+ unlisted.delete(answer);
857
+ listWatchers.delete(answer);
858
+ settle(listed);
859
+ };
860
+ unlisted.add(answer);
861
+ learned();
862
+ });
622
863
  },
623
864
  async read(conversation, options = {}) {
624
- const limit = limitOf(options);
625
- return deps.durably(async (local) => structuredClone(viewOf(conversation, await local.messageWindow(conversation, limit))));
865
+ const { text, ...windowed } = options;
866
+ const asked = askedOf(windowed);
867
+ const phrase = phraseOf(text);
868
+ return deps.durably(async (local) => {
869
+ const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before, phrase);
870
+ const total = asked.total ? await local.messageCount(conversation, phrase) : null;
871
+ return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total)));
872
+ });
626
873
  },
627
- create(input) {
628
- return deps.call('conversation.create', input);
874
+ async create(input) {
875
+ const result = await deps.call('conversations/create', input);
876
+ const conversation = answered(result, 'conversation_id');
877
+ if (conversation === null)
878
+ return result;
879
+ if (!listing.has(conversation))
880
+ creations.add(conversation);
881
+ const creation = creationOf(input);
882
+ if (creation !== null)
883
+ await moved(creation.agent, creation.operation, conversation);
884
+ return result;
629
885
  },
630
886
  async send(conversation, operation = changeBatchIdentity(), draft) {
887
+ if (deps.full())
888
+ return DURABLE_WRITE;
631
889
  if (deps.online()) {
632
- const { submission, earlier } = await submitted(conversation, operation, draft);
633
- if (!earlier || !deps.expired(operation))
634
- return dispatched(conversation, submission);
635
- await forgotten(conversation, submission);
636
- return EXPIRED;
890
+ const held = await submitted(conversation, operation, draft).catch((error) => {
891
+ if (refusedForSpace(error))
892
+ return null;
893
+ throw error;
894
+ });
895
+ if (held === null)
896
+ return DURABLE_WRITE;
897
+ return dispatched(conversation, held);
637
898
  }
638
899
  if (draft !== undefined) {
639
900
  await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
640
901
  ? undefined
641
- : returned(current ?? { conversation, text: '', files: [], revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
902
+ : returned(current ?? { conversation, text: '', revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
642
903
  }
643
904
  return OFFLINE;
644
905
  },
645
906
  answer(conversation, answer) {
646
- return invoke('conversation.answer', conversation, { answer });
907
+ return invoke('conversations/interaction/answer', conversation, {
908
+ operation_id: changeBatchIdentity(),
909
+ ...answer,
910
+ });
647
911
  },
648
912
  interrupt(conversation) {
649
- return invoke('conversation.interrupt', conversation);
913
+ return invoke('conversations/interrupt', conversation);
650
914
  },
651
915
  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;
916
+ return invoke('conversations/kill', conversation);
665
917
  },
666
918
  delete(conversation) {
667
- return invoke('conversation.delete', conversation);
919
+ return invoke('conversations/delete', conversation);
668
920
  },
669
921
  };
670
922
  const chatDraft = {
@@ -672,27 +924,36 @@ export function createConversationStore(deps) {
672
924
  return withDraft(conversation, (current) => ({
673
925
  conversation,
674
926
  text: draft.text,
675
- files: [...draft.files],
676
927
  revision,
677
- ...(current?.submitted === undefined ? {} : { submitted: current.submitted }),
928
+ ...(current === undefined ? {} : pending(current)),
929
+ }));
930
+ },
931
+ prepend(conversation, draft) {
932
+ return withDraft(conversation, (current) => ({
933
+ conversation,
934
+ ...(current === undefined ? { text: draft.text } : prepended(current, draft)),
935
+ revision: chatDraftRevision(),
936
+ ...(current === undefined ? {} : pending(current)),
678
937
  }));
679
938
  },
680
939
  discard(conversation) {
681
940
  return withDraft(conversation, (current) => current === undefined
682
941
  ? undefined
683
- : {
684
- conversation,
685
- text: '',
686
- files: [],
687
- revision: chatDraftRevision(),
688
- ...pending(current),
689
- });
942
+ : { conversation, text: '', revision: chatDraftRevision(), ...pending(current) });
690
943
  },
691
944
  };
692
945
  return {
693
946
  surface,
694
947
  chatDraft,
695
- hydrate(held, kept) {
948
+ hydrate(held, kept, list, cursor) {
949
+ if (list !== null && !listKnown) {
950
+ listing.clear();
951
+ for (const entry of list)
952
+ listing.set(entry.conversation, { ...entry });
953
+ listHeld = true;
954
+ listCursor = cursor;
955
+ deps.stream()?.hold(cursor);
956
+ }
696
957
  stored.clear();
697
958
  reads.clear();
698
959
  partials.clear();
@@ -706,6 +967,8 @@ export function createConversationStore(deps) {
706
967
  drafts.clear();
707
968
  for (const record of kept)
708
969
  drafts.set(record.conversation, record);
970
+ if (listHeld)
971
+ learned();
709
972
  },
710
973
  async reconcile() {
711
974
  if (!listKnown)
@@ -713,9 +976,11 @@ export function createConversationStore(deps) {
713
976
  const named = new Set(listing.keys());
714
977
  const leaving = [
715
978
  ...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
716
- ].filter((conversation) => !named.has(conversation));
979
+ ].filter((conversation) => !named.has(conversation) &&
980
+ !creations.has(conversation) &&
981
+ (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
717
982
  for (const conversation of named) {
718
- if (!replicated.has(conversation))
983
+ if (!replicated.has(conversation) && missed(conversation))
719
984
  behind.add(conversation);
720
985
  }
721
986
  replicated = named;
@@ -724,12 +989,25 @@ export function createConversationStore(deps) {
724
989
  await remove(leaving);
725
990
  },
726
991
  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);
992
+ if (deps.full()) {
993
+ if (behind.size > 0)
994
+ progress.complete = false;
995
+ return;
996
+ }
997
+ const owed = (conversation) => behind.has(conversation) && replicated.has(conversation);
998
+ const read = async (conversations) => {
999
+ if (conversations.length === 0 || deps.closed())
1000
+ return;
1001
+ const failed = await caughtUp(conversations);
730
1002
  if (failed?.kind === 'transport-uncertainty')
731
1003
  progress.reachable = false;
732
- }
1004
+ };
1005
+ const asking = [...behind].filter(owed).sort();
1006
+ const awaited = asking.filter((conversation) => catching.has(conversation));
1007
+ await Promise.all([
1008
+ read(asking.filter((conversation) => !catching.has(conversation))),
1009
+ Promise.all(awaited.map((conversation) => catching.get(conversation))).then(() => read(awaited.filter((conversation) => owed(conversation) && !catching.has(conversation)))),
1010
+ ]);
733
1011
  if (behind.size > 0)
734
1012
  progress.complete = false;
735
1013
  },
@@ -740,10 +1018,6 @@ export function createConversationStore(deps) {
740
1018
  continue;
741
1019
  if (deps.closed())
742
1020
  return;
743
- if (deps.expired(submission.operation)) {
744
- await forgotten(conversation, submission);
745
- continue;
746
- }
747
1021
  const result = await dispatched(conversation, submission);
748
1022
  if (result.kind === 'revoked-authorization')
749
1023
  return;
@@ -764,37 +1038,48 @@ export function createConversationStore(deps) {
764
1038
  known() {
765
1039
  return listKnown;
766
1040
  },
767
- reconnected() {
768
- for (const conversation of replicated)
769
- gap(conversation);
770
- },
771
1041
  reopened() {
772
- sending.clear();
1042
+ relisting = null;
1043
+ for (const [operation, dispatch] of sending)
1044
+ if (!dispatch.answered)
1045
+ sending.delete(operation);
1046
+ },
1047
+ lost() {
1048
+ catching.clear();
773
1049
  },
774
1050
  watch(conversation, options, deliver) {
775
- const limit = limitOf(options);
776
- const key = JSON.stringify([conversation, limit ?? null]);
1051
+ const asked = askedOf(options);
1052
+ const key = JSON.stringify([conversation, asked.limit, asked.after ?? null, asked.total]);
777
1053
  const view = views.get(key) ?? {
778
1054
  conversation,
779
- limit,
1055
+ asked,
780
1056
  watchers: new Set(),
781
1057
  window: null,
1058
+ more: false,
1059
+ total: null,
782
1060
  };
783
1061
  views.set(key, view);
784
1062
  const watcher = { notify: deliver, ready: false };
785
1063
  view.watchers.add(watcher);
786
- void deps
787
- .durably(async (local) => {
788
- view.window ??= await local.messageWindow(conversation, limit);
789
- })
1064
+ const reading = new AbortController();
1065
+ void untilAnswered(() => deps.durably(async (local) => {
1066
+ if (view.window !== null)
1067
+ return;
1068
+ const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
1069
+ view.window = messages;
1070
+ view.more = more;
1071
+ view.total = asked.total ? await local.messageCount(conversation) : null;
1072
+ }), reading.signal)
790
1073
  .then(() => {
791
1074
  if (deps.closed() || !view.watchers.has(watcher))
792
1075
  return;
793
1076
  watcher.ready = true;
794
- watcher.notify(viewOf(conversation, view.window ?? []));
1077
+ const window = view.window ?? [];
1078
+ watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
795
1079
  })
796
1080
  .catch(() => { });
797
1081
  return () => {
1082
+ reading.abort();
798
1083
  if (!view.watchers.delete(watcher))
799
1084
  return;
800
1085
  if (view.watchers.size === 0)
@@ -802,72 +1087,88 @@ export function createConversationStore(deps) {
802
1087
  };
803
1088
  },
804
1089
  watchList(deliver) {
805
- listWatchers.add(deliver);
806
- void agentsReady().then(() => {
807
- if (!deps.closed() && listWatchers.has(deliver))
808
- deliver(entries());
809
- });
1090
+ unlisted.add(deliver);
1091
+ learned();
810
1092
  return () => {
1093
+ unlisted.delete(deliver);
811
1094
  listWatchers.delete(deliver);
812
1095
  };
813
1096
  },
814
1097
  watchAgents(deliver) {
815
- agentWatchers.add(deliver);
816
- void agentsReady().then(() => {
817
- if (!deps.closed() && agentWatchers.has(deliver))
818
- deliver(agents());
819
- });
1098
+ unnamed.add(deliver);
1099
+ learned();
1100
+ readAgents();
820
1101
  return () => {
1102
+ unnamed.delete(deliver);
821
1103
  agentWatchers.delete(deliver);
822
1104
  };
823
1105
  },
1106
+ learned,
824
1107
  changed(set) {
825
1108
  for (const conversation of set.conversations)
826
1109
  emit(conversation);
827
- const declaration = deps.agents();
828
- if (declaration !== undefined &&
829
- set.documents.some((change) => change.locator === declaration.locator)) {
830
- agentsRead = readAgents().catch(() => {
831
- agentsRead = null;
832
- });
833
- }
834
1110
  },
835
1111
  receive(event) {
836
1112
  if (deps.closed())
837
1113
  return false;
838
1114
  if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
839
1115
  if (event.kind === 'list') {
840
- listing.clear();
841
- for (const entry of event.entries)
842
- listing.set(entry.conversation, { ...entry });
1116
+ if (event.whole)
1117
+ relisting ??= new Map();
1118
+ for (const entry of event.entries) {
1119
+ if (relisting === null)
1120
+ listed(entry);
1121
+ else
1122
+ relisting.set(entry.conversation, { ...entry });
1123
+ }
1124
+ if (event.more)
1125
+ return false;
1126
+ if (relisting !== null) {
1127
+ listing.clear();
1128
+ for (const entry of relisting.values())
1129
+ listed(entry);
1130
+ relisting = null;
1131
+ }
1132
+ listCursor = event.cursor;
1133
+ deps.stream()?.hold(event.cursor);
843
1134
  listKnown = true;
1135
+ for (const conversation of replicated) {
1136
+ if (!listing.has(conversation))
1137
+ continue;
1138
+ if (!missed(conversation))
1139
+ behind.delete(conversation);
1140
+ else if (!behind.has(conversation))
1141
+ gap(conversation);
1142
+ }
1143
+ openingEnded();
844
1144
  }
845
1145
  else if (event.kind === 'listed') {
846
- listing.set(event.entry.conversation, { ...event.entry });
1146
+ listed(event.entry);
1147
+ relisting?.set(event.entry.conversation, { ...event.entry });
847
1148
  }
848
1149
  else {
849
1150
  listing.delete(event.conversation);
1151
+ relisting?.delete(event.conversation);
850
1152
  }
1153
+ keepList();
851
1154
  emitList();
852
- return (event.kind !== 'list' &&
1155
+ return ((event.kind !== 'list' &&
853
1156
  listKnown &&
854
1157
  (listing.size !== replicated.size ||
855
- [...listing.keys()].some((conversation) => !replicated.has(conversation))));
1158
+ [...listing.keys()].some((conversation) => !replicated.has(conversation)))) ||
1159
+ (listKnown && [...drafts.keys()].some(retiredChat)));
856
1160
  }
857
1161
  heard(event);
858
1162
  return false;
859
1163
  },
860
- searchable() {
861
- return [...(listKnown ? listing.keys() : stored)].sort();
862
- },
863
- acquired(conversation) {
864
- return reads.has(conversation) && !partials.has(conversation);
865
- },
866
1164
  close() {
867
1165
  views.clear();
868
1166
  lives.clear();
1167
+ unsent.clear();
869
1168
  listWatchers.clear();
1169
+ unlisted.clear();
870
1170
  agentWatchers.clear();
1171
+ unnamed.clear();
871
1172
  },
872
1173
  };
873
1174
  }