@agentshouse/mdruntime 0.1.0-cd50a4607cb7d → 0.1.0-cd9e28a590f84

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