@agentshouse/mdruntime 0.1.0-ccd81b825bedf → 0.1.0-cce1d81ef3d6d

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