@agentshouse/mdruntime 0.1.0-ca6edc1abd618 → 0.1.0-ca8707258fdde

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 (220) hide show
  1. package/README.md +2 -2
  2. package/dist/acceptance.d.ts +26 -0
  3. package/dist/acceptance.d.ts.map +1 -0
  4. package/dist/acceptance.js +384 -0
  5. package/dist/adapter.d.ts +102 -70
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/attached-model.d.ts +3 -10
  8. package/dist/attached-model.d.ts.map +1 -1
  9. package/dist/attached-model.js +29 -47
  10. package/dist/bootstrap.d.ts +16 -14
  11. package/dist/bootstrap.d.ts.map +1 -1
  12. package/dist/bootstrap.js +272 -129
  13. package/dist/chain.d.ts +2 -1
  14. package/dist/chain.d.ts.map +1 -1
  15. package/dist/change.d.ts +1 -0
  16. package/dist/change.d.ts.map +1 -1
  17. package/dist/chat-types.d.ts +3 -0
  18. package/dist/chat-types.d.ts.map +1 -0
  19. package/dist/chat-types.js +2 -0
  20. package/dist/client.d.ts +20 -10
  21. package/dist/client.d.ts.map +1 -1
  22. package/dist/client.js +55 -6
  23. package/dist/collection.d.ts +7 -4
  24. package/dist/collection.d.ts.map +1 -1
  25. package/dist/collection.js +23 -17
  26. package/dist/compile.d.ts +6 -6
  27. package/dist/compile.d.ts.map +1 -1
  28. package/dist/compile.js +278 -282
  29. package/dist/connection.d.ts +22 -44
  30. package/dist/connection.d.ts.map +1 -1
  31. package/dist/connection.js +1600 -3577
  32. package/dist/context.d.ts +2 -0
  33. package/dist/context.d.ts.map +1 -0
  34. package/dist/context.js +7 -0
  35. package/dist/conversation-row.d.ts +33 -0
  36. package/dist/conversation-row.d.ts.map +1 -0
  37. package/dist/conversation-row.js +47 -0
  38. package/dist/conversation.d.ts +27 -18
  39. package/dist/conversation.d.ts.map +1 -1
  40. package/dist/conversation.js +484 -258
  41. package/dist/durability.d.ts +6 -1
  42. package/dist/durability.d.ts.map +1 -1
  43. package/dist/entry.d.ts +3 -2
  44. package/dist/entry.d.ts.map +1 -1
  45. package/dist/entry.js +18 -8
  46. package/dist/figures.d.ts +48 -0
  47. package/dist/figures.d.ts.map +1 -0
  48. package/dist/figures.js +197 -0
  49. package/dist/file.d.ts +2 -11
  50. package/dist/file.d.ts.map +1 -1
  51. package/dist/file.js +6 -4
  52. package/dist/host.d.ts +3 -7
  53. package/dist/host.d.ts.map +1 -1
  54. package/dist/host.js +29 -32
  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 +20 -10
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +6 -1
  61. package/dist/install.d.ts +86 -0
  62. package/dist/install.d.ts.map +1 -0
  63. package/dist/install.js +537 -0
  64. package/dist/job.d.ts +33 -0
  65. package/dist/job.d.ts.map +1 -0
  66. package/dist/job.js +54 -0
  67. package/dist/live.d.ts +5 -4
  68. package/dist/live.d.ts.map +1 -1
  69. package/dist/live.js +62 -20
  70. package/dist/log.d.ts +1 -0
  71. package/dist/log.d.ts.map +1 -1
  72. package/dist/media.d.ts +8 -0
  73. package/dist/media.d.ts.map +1 -0
  74. package/dist/media.js +218 -0
  75. package/dist/memory.d.ts +12 -5
  76. package/dist/memory.d.ts.map +1 -1
  77. package/dist/memory.js +216 -85
  78. package/dist/messages.d.ts +27 -3
  79. package/dist/messages.d.ts.map +1 -1
  80. package/dist/messages.js +97 -19
  81. package/dist/model.d.ts +8 -10
  82. package/dist/model.d.ts.map +1 -1
  83. package/dist/model.js +5 -17
  84. package/dist/observation.d.ts +3 -2
  85. package/dist/observation.d.ts.map +1 -1
  86. package/dist/observation.js +8 -3
  87. package/dist/outcome.d.ts +14 -5
  88. package/dist/outcome.d.ts.map +1 -1
  89. package/dist/outcome.js +19 -2
  90. package/dist/overlay.d.ts +1 -0
  91. package/dist/overlay.d.ts.map +1 -1
  92. package/dist/overlay.js +11 -0
  93. package/dist/parts.d.ts +59 -0
  94. package/dist/parts.d.ts.map +1 -0
  95. package/dist/parts.js +118 -0
  96. package/dist/paths.d.ts +1 -0
  97. package/dist/paths.d.ts.map +1 -1
  98. package/dist/paths.js +4 -0
  99. package/dist/query.d.ts +18 -207
  100. package/dist/query.d.ts.map +1 -1
  101. package/dist/query.js +3 -505
  102. package/dist/questions.d.ts +32 -0
  103. package/dist/questions.d.ts.map +1 -0
  104. package/dist/questions.js +118 -0
  105. package/dist/reach.d.ts +18 -11
  106. package/dist/reach.d.ts.map +1 -1
  107. package/dist/reach.js +206 -126
  108. package/dist/react.d.ts +5 -2
  109. package/dist/react.d.ts.map +1 -1
  110. package/dist/read.d.ts +7 -0
  111. package/dist/read.d.ts.map +1 -0
  112. package/dist/read.js +17 -0
  113. package/dist/readiness.d.ts +3 -17
  114. package/dist/readiness.d.ts.map +1 -1
  115. package/dist/readiness.js +4 -10
  116. package/dist/reads.d.ts +34 -10
  117. package/dist/reads.d.ts.map +1 -1
  118. package/dist/reads.js +114 -86
  119. package/dist/recovery.d.ts +1 -0
  120. package/dist/recovery.d.ts.map +1 -1
  121. package/dist/recovery.js +35 -1
  122. package/dist/refresh.d.ts +7 -5
  123. package/dist/refresh.d.ts.map +1 -1
  124. package/dist/refresh.js +33 -5
  125. package/dist/replica.d.ts +10 -11
  126. package/dist/replica.d.ts.map +1 -1
  127. package/dist/replica.js +52 -20
  128. package/dist/room-draft.d.ts +21 -5
  129. package/dist/room-draft.d.ts.map +1 -1
  130. package/dist/room-draft.js +98 -51
  131. package/dist/room-drafts.d.ts +29 -0
  132. package/dist/room-drafts.d.ts.map +1 -0
  133. package/dist/room-drafts.js +216 -0
  134. package/dist/rows.d.ts +18 -16
  135. package/dist/rows.d.ts.map +1 -1
  136. package/dist/rows.js +45 -153
  137. package/dist/save.d.ts +2 -0
  138. package/dist/save.d.ts.map +1 -1
  139. package/dist/save.js +22 -13
  140. package/dist/saves.d.ts +96 -0
  141. package/dist/saves.d.ts.map +1 -0
  142. package/dist/saves.js +999 -0
  143. package/dist/search.d.ts +12 -63
  144. package/dist/search.d.ts.map +1 -1
  145. package/dist/search.js +9 -52
  146. package/dist/seed.d.ts +16 -2
  147. package/dist/seed.d.ts.map +1 -1
  148. package/dist/seed.js +66 -0
  149. package/dist/shared.d.ts +0 -1
  150. package/dist/shared.d.ts.map +1 -1
  151. package/dist/shared.js +0 -10
  152. package/dist/socket/admission.d.ts +7 -0
  153. package/dist/socket/admission.d.ts.map +1 -0
  154. package/dist/socket/admission.js +39 -0
  155. package/dist/socket/answers.d.ts +6 -0
  156. package/dist/socket/answers.d.ts.map +1 -0
  157. package/dist/socket/answers.js +20 -0
  158. package/dist/socket/reads.d.ts +10 -0
  159. package/dist/socket/reads.d.ts.map +1 -0
  160. package/dist/socket/reads.js +31 -0
  161. package/dist/socket.d.ts +43 -0
  162. package/dist/socket.d.ts.map +1 -0
  163. package/dist/socket.js +96 -0
  164. package/dist/source.d.ts +61 -0
  165. package/dist/source.d.ts.map +1 -0
  166. package/dist/source.js +81 -0
  167. package/dist/status.d.ts +4 -5
  168. package/dist/status.d.ts.map +1 -1
  169. package/dist/status.js +3 -7
  170. package/dist/store/fences.d.ts +16 -0
  171. package/dist/store/fences.d.ts.map +1 -0
  172. package/dist/store/fences.js +26 -0
  173. package/dist/store/installs.d.ts +30 -0
  174. package/dist/store/installs.d.ts.map +1 -0
  175. package/dist/store/installs.js +34 -0
  176. package/dist/store/records.d.ts +212 -0
  177. package/dist/store/records.d.ts.map +1 -0
  178. package/dist/store/records.js +408 -0
  179. package/dist/store/room-held.d.ts +8 -0
  180. package/dist/store/room-held.d.ts.map +1 -0
  181. package/dist/store/room-held.js +17 -0
  182. package/dist/store/watchers.d.ts +15 -0
  183. package/dist/store/watchers.d.ts.map +1 -0
  184. package/dist/store/watchers.js +36 -0
  185. package/dist/store/writes.d.ts +24 -0
  186. package/dist/store/writes.d.ts.map +1 -0
  187. package/dist/store/writes.js +125 -0
  188. package/dist/store.d.ts +76 -158
  189. package/dist/store.d.ts.map +1 -1
  190. package/dist/store.js +117 -403
  191. package/dist/stored.d.ts +66 -55
  192. package/dist/stored.d.ts.map +1 -1
  193. package/dist/stored.js +343 -273
  194. package/dist/tables.d.ts +118 -65
  195. package/dist/tables.d.ts.map +1 -1
  196. package/dist/tables.js +199 -99
  197. package/dist/tallies.d.ts +18 -0
  198. package/dist/tallies.d.ts.map +1 -0
  199. package/dist/tallies.js +129 -0
  200. package/dist/transfer.d.ts +0 -5
  201. package/dist/transfer.d.ts.map +1 -1
  202. package/dist/validation.d.ts +3 -4
  203. package/dist/validation.d.ts.map +1 -1
  204. package/dist/validation.js +42 -100
  205. package/dist/windows.d.ts +1 -12
  206. package/dist/windows.d.ts.map +1 -1
  207. package/dist/windows.js +2 -75
  208. package/dist/wire.d.ts +23 -8
  209. package/dist/wire.d.ts.map +1 -1
  210. package/dist/wire.js +8 -2
  211. package/package.json +2 -2
  212. package/dist/fence.d.ts +0 -6
  213. package/dist/fence.d.ts.map +0 -1
  214. package/dist/fence.js +0 -10
  215. package/dist/fulltext.d.ts +0 -27
  216. package/dist/fulltext.d.ts.map +0 -1
  217. package/dist/fulltext.js +0 -368
  218. package/dist/words.d.ts +0 -19
  219. package/dist/words.d.ts.map +0 -1
  220. package/dist/words.js +0 -150
@@ -1,10 +1,14 @@
1
+ import { cursorOf, decodedCursor } from '@agentshouse/mdmodel';
1
2
  import { conversationChangeSet } from './change-set.js';
3
+ import { heldFacts } from './conversation-row.js';
2
4
  import { runtimeError } from './failure.js';
3
5
  import { refusedForSpace } from './durability.js';
4
- import { declaredFile } from './file.js';
5
6
  import { changeBatchIdentity, chatDraftRevision } from './identity.js';
6
7
  import { OFFLINE } from './outcome.js';
7
- import { capped, cursorOf, decodedCursor, FROM_START, messageOrder, precedes } from './windows.js';
8
+ import { jobsOf, runningLine } from './parts.js';
9
+ import { openQuestion } from './questions.js';
10
+ import { untilAnswered } from './recovery.js';
11
+ import { capped, FROM_START, messageOrder, precedes } from './windows.js';
8
12
  const QUIET = {
9
13
  turn: null,
10
14
  questions: [],
@@ -12,6 +16,7 @@ const QUIET = {
12
16
  commands: [],
13
17
  options: [],
14
18
  starting: null,
19
+ subagents: [],
15
20
  link: null,
16
21
  deliveryUnknown: [],
17
22
  };
@@ -21,6 +26,7 @@ const FACTS = [
21
26
  'commands',
22
27
  'options',
23
28
  'starting',
29
+ 'subagents',
24
30
  'link',
25
31
  'deliveryUnknown',
26
32
  ];
@@ -31,27 +37,30 @@ export function unheldView(conversation) {
31
37
  turn: null,
32
38
  questions: [],
33
39
  process: null,
40
+ processEnded: null,
34
41
  commands: [],
35
42
  options: [],
36
43
  starting: null,
44
+ subagents: [],
37
45
  link: null,
38
46
  deliveryUnknown: [],
39
47
  chatDraft: null,
48
+ jobs: [],
40
49
  };
41
50
  }
42
51
  export const SESSION_STARTED = 'Session started';
43
52
  const NEW_CHAT = 'new:';
44
53
  export const HISTORY_CONVERSATIONS = 64;
54
+ // A row is read at a glance and each telling clones the list to every observing tab, while a running line changes at each Kit flush.
55
+ const ROW_INTERVAL_MS = 1_000;
45
56
  export function newChat(agent) {
46
57
  return `${NEW_CHAT}${agent}`;
47
58
  }
48
59
  function creationOf(input) {
49
60
  if (typeof input !== 'object' || input === null)
50
61
  return null;
51
- const { agent_id: agent, operation_id: operation, replaces, } = input;
52
- return typeof agent === 'string' && typeof operation === 'string' && replaces === undefined
53
- ? { agent, operation }
54
- : null;
62
+ const { agent_id: agent, operation_id: operation } = input;
63
+ return typeof agent === 'string' && typeof operation === 'string' ? { agent, operation } : null;
55
64
  }
56
65
  function startedTurn(turn, operation) {
57
66
  if (operation.op === 'begin')
@@ -210,7 +219,14 @@ function askedOf(options) {
210
219
  }
211
220
  return { limit: capped(limit), after, before: beforeOf(after), total: total === true };
212
221
  }
213
- const EXPIRED = { kind: 'rejected', code: 'expired', fields: {}, retryable: false };
222
+ function phraseOf(text) {
223
+ if (text === undefined)
224
+ return null;
225
+ if (typeof text !== 'string' || text === '') {
226
+ throw runtimeError('invalid-text', 'a conversation is found by a phrase');
227
+ }
228
+ return text;
229
+ }
214
230
  const DURABLE_WRITE = {
215
231
  kind: 'rejected',
216
232
  code: 'durable-write',
@@ -244,10 +260,6 @@ function returned(draft, submission, others) {
244
260
  }, others);
245
261
  }
246
262
  export function createConversationStore(deps) {
247
- const stored = new Set();
248
- const reads = new Map();
249
- const partials = new Set();
250
- const drafts = new Map();
251
263
  const sending = new Map();
252
264
  const behind = new Set();
253
265
  const catching = new Map();
@@ -255,21 +267,27 @@ export function createConversationStore(deps) {
255
267
  const lives = new Map();
256
268
  const unsent = new Map();
257
269
  const views = new Map();
258
- const listing = new Map();
270
+ const heardNewest = new Map();
271
+ let relisting = null;
259
272
  const creations = new Set();
260
273
  const listWatchers = new Set();
274
+ const unlisted = new Set();
261
275
  let listKnown = false;
276
+ let listOwed = false;
277
+ let listTelling = false;
278
+ let rowTimer = null;
279
+ const closing = new AbortController();
262
280
  const agentWatchers = new Set();
263
- let agentNames = new Map();
264
- let retiredAgents = new Set();
265
- let agentsRead = null;
281
+ const unnamed = new Set();
282
+ let agentsShown = null;
283
+ let listedAgents = null;
284
+ let agentsReading = false;
266
285
  let replicated = new Set();
267
286
  let handedReplicated;
268
287
  function liveOf(conversation) {
269
288
  return lives.get(conversation) ?? QUIET;
270
289
  }
271
- function viewOf(conversation, held, frame) {
272
- const draft = drafts.get(conversation);
290
+ function viewOf(conversation, held, frame, draft, ended, marked) {
273
291
  const facts = liveOf(conversation);
274
292
  return {
275
293
  conversation,
@@ -283,14 +301,17 @@ export function createConversationStore(deps) {
283
301
  turn: structuredClone(facts.turn),
284
302
  questions: structuredClone(facts.questions),
285
303
  process: facts.process === null ? null : { ...facts.process },
304
+ processEnded: ended,
286
305
  commands: structuredClone(facts.commands),
287
306
  options: structuredClone(facts.options),
288
307
  starting: facts.starting,
308
+ subagents: structuredClone(facts.subagents),
289
309
  link: facts.link === null ? null : { ...facts.link },
290
310
  deliveryUnknown: [...facts.deliveryUnknown],
291
- chatDraft: draft === undefined || unwritten(draft)
292
- ? null
293
- : { text: draft.text, revision: draft.revision },
311
+ chatDraft: draft === null || unwritten(draft) ? null : { text: draft.text, revision: draft.revision },
312
+ jobs: facts.process?.running === false
313
+ ? []
314
+ : jobsOf(marked.filter(({ at }) => ended === null || at > ended), facts.turn),
294
315
  };
295
316
  }
296
317
  function emit(conversation) {
@@ -302,7 +323,7 @@ export function createConversationStore(deps) {
302
323
  const ready = [...view.watchers].filter((watcher) => watcher.ready);
303
324
  if (ready.length === 0)
304
325
  continue;
305
- const value = viewOf(conversation, view.window, sharedFrame(view, view.window));
326
+ const value = viewOf(conversation, view.window, sharedFrame(view, view.window), view.draft, view.ended, view.marked);
306
327
  for (const watcher of ready)
307
328
  watcher.notify(value);
308
329
  }
@@ -312,11 +333,13 @@ export function createConversationStore(deps) {
312
333
  }
313
334
  async function slide(local, conversation, arriving) {
314
335
  let count;
336
+ let marked;
315
337
  for (const view of views.values()) {
316
338
  if (view.conversation !== conversation || view.window === null)
317
339
  continue;
318
340
  if (arriving === null) {
319
341
  view.window = [];
342
+ view.marked = [];
320
343
  view.more = false;
321
344
  if (view.total !== null)
322
345
  view.total = 0;
@@ -326,6 +349,10 @@ export function createConversationStore(deps) {
326
349
  const next = windowOf(view.window, joining, view.asked.limit);
327
350
  view.window = next.messages;
328
351
  view.more ||= next.dropped;
352
+ if (arriving.length > 0) {
353
+ marked ??= await local.jobMarkers(conversation);
354
+ view.marked = marked;
355
+ }
329
356
  if (view.total !== null && arriving.length > 0) {
330
357
  count ??= await local.messageCount(conversation);
331
358
  view.total = count;
@@ -338,102 +365,234 @@ export function createConversationStore(deps) {
338
365
  return 'asking';
339
366
  return facts.turn === null ? 'idle' : 'working';
340
367
  }
341
- function entries() {
342
- return [...listing.values()].map((entry) => ({
368
+ function rowOf(conversation) {
369
+ const turn = lives.get(conversation)?.turn ?? null;
370
+ return {
371
+ state: stateOf(conversation),
372
+ started: turn?.started ?? null,
373
+ line: turn === null ? null : runningLine(turn.parts),
374
+ question: openQuestion(liveOf(conversation).questions),
375
+ };
376
+ }
377
+ async function entries() {
378
+ const list = await heldList();
379
+ if (list === null)
380
+ return null;
381
+ const held = list.length === 0
382
+ ? []
383
+ : await deps.durably((local) => local.conversationRows(list.map(({ conversation }) => conversation)));
384
+ const rows = new Map(held.map((one) => [one.conversation, one]));
385
+ return list.map((entry) => ({
343
386
  conversation: entry.conversation,
344
- agent: { identity: entry.agent, name: agentNames.get(entry.agent) ?? null },
387
+ agent: { identity: entry.agent, name: entry.agentName },
345
388
  name: entry.name,
346
389
  created: entry.created,
347
390
  finished: entry.finished,
348
391
  outcome: entry.outcome,
349
392
  waiting: entry.waiting,
350
- state: stateOf(entry.conversation),
351
- started: lives.get(entry.conversation)?.turn?.started ?? null,
393
+ ...rowOf(entry.conversation),
394
+ ...heldFacts(rows.get(entry.conversation), entry),
352
395
  }));
353
396
  }
397
+ async function heldList() {
398
+ const stored = (await (await deps.local()).facts()).conversationList;
399
+ if (stored !== null)
400
+ return stored;
401
+ return !listKnown && deps.stream() !== undefined && deps.online() ? null : [];
402
+ }
403
+ async function endedIn(conversation) {
404
+ const list = await heldList();
405
+ return list?.find((entry) => entry.conversation === conversation)?.processEnded ?? null;
406
+ }
407
+ function listChanged(change) {
408
+ void deps
409
+ .durably(async (local) => {
410
+ const { conversationList, conversationCursor } = await local.facts();
411
+ const kept = change.replacing ? [] : (conversationList ?? []);
412
+ const held = new Map(kept.map((entry) => [entry.conversation, entry]));
413
+ for (const entry of change.set) {
414
+ held.set(entry.conversation, { ...entry });
415
+ creations.delete(entry.conversation);
416
+ }
417
+ if (change.gone !== null)
418
+ held.delete(change.gone);
419
+ const list = [...held.values()];
420
+ if (JSON.stringify(list) !== JSON.stringify(conversationList)) {
421
+ await local.fact('conversationList', list);
422
+ }
423
+ if (change.cursor !== undefined) {
424
+ if (change.cursor !== conversationCursor) {
425
+ await local.fact('conversationCursor', change.cursor);
426
+ }
427
+ deps.stream()?.hold(change.cursor);
428
+ }
429
+ return list;
430
+ })
431
+ .catch(async () => (await (await deps.local()).facts()).conversationList ?? [])
432
+ .then((list) => {
433
+ if (deps.closed() || change.paged === true)
434
+ return;
435
+ endedAnew(list);
436
+ if (change.cursor !== undefined) {
437
+ listKnown = true;
438
+ settleBehind(list);
439
+ openingEnded();
440
+ deps.settled();
441
+ }
442
+ emitList();
443
+ if (!listKnown)
444
+ return;
445
+ retiredDrafts(list);
446
+ if (change.cursor === undefined &&
447
+ (list.length !== replicated.size ||
448
+ list.some(({ conversation }) => !replicated.has(conversation)))) {
449
+ deps.again();
450
+ }
451
+ }, () => { });
452
+ }
453
+ function endedAnew(list) {
454
+ const entries = new Map(list.map((entry) => [entry.conversation, entry]));
455
+ for (const view of views.values()) {
456
+ const ended = entries.get(view.conversation)?.processEnded ?? null;
457
+ if (view.window === null || ended === view.ended)
458
+ continue;
459
+ view.ended = ended;
460
+ emit(view.conversation);
461
+ }
462
+ }
463
+ let listShown = Promise.resolve();
464
+ function shownList(work) {
465
+ const reading = heldList();
466
+ listShown = listShown.then(async () => {
467
+ const list = await reading.catch(() => null);
468
+ if (list === null || deps.closed())
469
+ return;
470
+ work(list);
471
+ });
472
+ }
473
+ async function told() {
474
+ try {
475
+ while (listOwed && !deps.closed()) {
476
+ listOwed = false;
477
+ const value = await untilAnswered(entries, closing.signal);
478
+ if (deps.closed())
479
+ return;
480
+ if (value === null)
481
+ continue;
482
+ for (const watcher of listWatchers)
483
+ watcher(value);
484
+ for (const watcher of [...unlisted]) {
485
+ unlisted.delete(watcher);
486
+ listWatchers.add(watcher);
487
+ watcher(value);
488
+ }
489
+ }
490
+ }
491
+ finally {
492
+ listTelling = false;
493
+ }
494
+ }
495
+ function tellList() {
496
+ if (deps.closed() || (listWatchers.size === 0 && unlisted.size === 0))
497
+ return;
498
+ listOwed = true;
499
+ if (listTelling)
500
+ return;
501
+ listTelling = true;
502
+ void told().catch(() => { });
503
+ }
504
+ function tellSoon() {
505
+ if (rowTimer !== null)
506
+ return;
507
+ rowTimer = setTimeout(() => {
508
+ rowTimer = null;
509
+ tellList();
510
+ }, ROW_INTERVAL_MS);
511
+ }
354
512
  function emitList() {
355
- if (deps.closed() || listWatchers.size === 0)
513
+ if (deps.closed())
356
514
  return;
357
- const value = entries();
358
- for (const watcher of listWatchers)
359
- watcher(value);
515
+ tellList();
516
+ emitAgents();
517
+ learned();
360
518
  }
361
- function agents() {
362
- return [...agentNames]
363
- .filter(([identity]) => !retiredAgents.has(identity))
364
- .map(([identity, name]) => ({ identity, name }))
519
+ function learned() {
520
+ if (deps.closed())
521
+ return;
522
+ if (unlisted.size > 0)
523
+ tellList();
524
+ if (unnamed.size === 0)
525
+ return;
526
+ shownList((list) => {
527
+ const value = agents(list);
528
+ for (const watcher of [...unnamed]) {
529
+ unnamed.delete(watcher);
530
+ agentWatchers.add(watcher);
531
+ watcher(value);
532
+ }
533
+ });
534
+ }
535
+ function agents(list) {
536
+ const named = new Map();
537
+ for (const agent of listedAgents ?? [])
538
+ named.set(agent.agent, agent);
539
+ for (const entry of list) {
540
+ named.set(entry.agent, { name: entry.agentName, retired: entry.agentRetired });
541
+ }
542
+ return [...named]
543
+ .filter(([, agent]) => !agent.retired)
544
+ .map(([identity, agent]) => ({ identity, name: agent.name }))
365
545
  .sort((left, right) => left.name === right.name
366
546
  ? left.identity.localeCompare(right.identity)
367
547
  : left.name.localeCompare(right.name));
368
548
  }
549
+ function readAgents() {
550
+ const stream = deps.stream();
551
+ if (stream?.agents === undefined || !deps.online() || agentsReading)
552
+ return;
553
+ agentsReading = true;
554
+ void stream
555
+ .agents()
556
+ .then((read) => {
557
+ if (read === null)
558
+ return;
559
+ listedAgents = read;
560
+ emitAgents();
561
+ }, () => { })
562
+ .finally(() => {
563
+ agentsReading = false;
564
+ });
565
+ }
369
566
  function emitAgents() {
370
567
  if (deps.closed() || agentWatchers.size === 0)
371
568
  return;
372
- const value = agents();
373
- for (const watcher of agentWatchers)
374
- watcher(value);
569
+ shownList((list) => {
570
+ const value = agents(list);
571
+ const shown = JSON.stringify(value);
572
+ if (shown === agentsShown)
573
+ return;
574
+ agentsShown = shown;
575
+ for (const watcher of agentWatchers)
576
+ watcher(value);
577
+ });
375
578
  }
376
579
  function setLive(conversation, next) {
377
- const before = stateOf(conversation);
378
- const began = lives.get(conversation)?.turn?.started;
580
+ const before = rowOf(conversation);
379
581
  if (next === null)
380
582
  lives.delete(conversation);
381
583
  else
382
584
  lives.set(conversation, next);
383
- if (!listing.has(conversation))
384
- return;
385
- if (stateOf(conversation) !== before || next?.turn?.started !== began)
585
+ const after = rowOf(conversation);
586
+ if (after.state !== before.state || after.started !== before.started)
386
587
  emitList();
588
+ else if (after.line !== before.line || after.question !== before.question)
589
+ tellSoon();
387
590
  }
388
- async function readAgents() {
389
- const declaration = deps.agents();
390
- if (declaration === undefined)
391
- return;
392
- const names = new Map();
393
- const retired = new Set();
394
- let covered = null;
395
- for (let after;;) {
396
- const read = await deps.documents(declaration, after);
397
- if ('pending' in read)
398
- return;
399
- const coverage = JSON.stringify(read.coverage);
400
- if (covered !== null && coverage !== covered)
401
- return readAgents();
402
- covered = coverage;
403
- for (const document of read.documents) {
404
- const identity = document.fields?.[declaration.identity];
405
- const name = document.fields?.[declaration.name];
406
- if (typeof identity !== 'string' || typeof name !== 'string')
407
- continue;
408
- names.set(identity, name);
409
- const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
410
- if (ended !== undefined && ended !== null)
411
- retired.add(identity);
412
- }
413
- if (read.next === undefined)
414
- break;
415
- after = read.next;
416
- }
417
- const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
418
- if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents]))
419
- return;
420
- agentNames = names;
421
- retiredAgents = retired;
422
- if (named)
423
- emitList();
424
- emitAgents();
425
- const ended = [...drafts.keys()].filter(retiredChat);
426
- if (ended.length > 0)
427
- await remove(ended);
428
- }
429
- function retiredChat(conversation) {
430
- return (conversation.startsWith(NEW_CHAT) && retiredAgents.has(conversation.slice(NEW_CHAT.length)));
431
- }
432
- function agentsReady() {
433
- agentsRead ??= readAgents().catch(() => {
434
- agentsRead = null;
435
- });
436
- return agentsRead;
591
+ function retiredChat(conversation, list) {
592
+ if (!conversation.startsWith(NEW_CHAT))
593
+ return false;
594
+ const agent = conversation.slice(NEW_CHAT.length);
595
+ return list.some((entry) => entry.agent === agent && entry.agentRetired);
437
596
  }
438
597
  function live(conversation, change) {
439
598
  const owed = unsent.get(conversation);
@@ -456,6 +615,7 @@ export function createConversationStore(deps) {
456
615
  commands: facts.has('commands') ? QUIET.commands : held.commands,
457
616
  options: facts.has('options') ? QUIET.options : held.options,
458
617
  starting: facts.has('starting') ? QUIET.starting : held.starting,
618
+ subagents: facts.has('subagents') ? QUIET.subagents : held.subagents,
459
619
  link: facts.has('link') ? QUIET.link : held.link,
460
620
  deliveryUnknown: facts.has('deliveryUnknown')
461
621
  ? QUIET.deliveryUnknown
@@ -490,17 +650,23 @@ export function createConversationStore(deps) {
490
650
  }
491
651
  async function remove(leaving) {
492
652
  await deps.durably(async (local) => {
653
+ const kept = new Set([
654
+ ...(await local.conversations(leaving)).map(({ conversation }) => conversation),
655
+ ...(await local.chatDrafts())
656
+ .filter(({ conversation }) => leaving.includes(conversation))
657
+ .map(({ conversation }) => conversation),
658
+ ]);
493
659
  await local.dropConversations(leaving);
494
660
  const changed = [];
495
661
  for (const conversation of leaving) {
496
- if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
662
+ if (kept.has(conversation) || lives.has(conversation))
497
663
  changed.push(conversation);
664
+ for (const view of views.values()) {
665
+ if (view.conversation === conversation)
666
+ view.draft = null;
498
667
  }
499
- stored.delete(conversation);
500
- reads.delete(conversation);
501
- partials.delete(conversation);
502
- drafts.delete(conversation);
503
668
  behind.delete(conversation);
669
+ heardNewest.delete(conversation);
504
670
  unsent.delete(conversation);
505
671
  setLive(conversation, null);
506
672
  await slide(local, conversation, null);
@@ -509,6 +675,69 @@ export function createConversationStore(deps) {
509
675
  deps.publish(conversationChangeSet(changed));
510
676
  });
511
677
  }
678
+ function newestOfListed(entry) {
679
+ if (entry === undefined)
680
+ return undefined;
681
+ const heard = heardNewest.get(entry.conversation);
682
+ if (heard === undefined)
683
+ return entry.newest;
684
+ return entry.newest === null || heard > entry.newest ? heard : entry.newest;
685
+ }
686
+ function missed(conversation, held, entry) {
687
+ const read = held?.read;
688
+ const newest = newestOfListed(entry);
689
+ return (read === undefined ||
690
+ newest === undefined ||
691
+ held?.partial === true ||
692
+ (newest !== null && (read === null || newest > read)));
693
+ }
694
+ async function readsOf(local, conversations) {
695
+ return new Map((await local.conversations(conversations)).map((held) => [held.conversation, held]));
696
+ }
697
+ function retiredDrafts(list) {
698
+ if (!list.some((entry) => entry.agentRetired))
699
+ return;
700
+ void deps
701
+ .local()
702
+ .then((local) => local.chatDrafts())
703
+ .then((held) => {
704
+ if (!deps.closed() && held.some(({ conversation }) => retiredChat(conversation, list))) {
705
+ deps.again();
706
+ }
707
+ }, () => { });
708
+ }
709
+ function settleBehind(list) {
710
+ void deps
711
+ .durably(async (local) => {
712
+ const entries = new Map(list.map((entry) => [entry.conversation, entry]));
713
+ const named = [...replicated].filter((conversation) => entries.has(conversation));
714
+ const held = await readsOf(local, named);
715
+ let owed = false;
716
+ for (const conversation of named) {
717
+ if (!missed(conversation, held.get(conversation), entries.get(conversation))) {
718
+ behind.delete(conversation);
719
+ }
720
+ else if (!behind.has(conversation)) {
721
+ gap(conversation);
722
+ owed = true;
723
+ }
724
+ }
725
+ return owed;
726
+ })
727
+ .then((owed) => {
728
+ if (deps.closed())
729
+ return;
730
+ deps.settled();
731
+ if (owed)
732
+ deps.again();
733
+ }, () => { });
734
+ }
735
+ function noted(conversation, message) {
736
+ const heard = heardNewest.get(conversation);
737
+ if (heard === undefined || message.position > heard) {
738
+ heardNewest.set(conversation, message.position);
739
+ }
740
+ }
512
741
  function gap(conversation) {
513
742
  gaps.set(conversation, (gaps.get(conversation) ?? 0) + 1);
514
743
  behind.add(conversation);
@@ -517,7 +746,7 @@ export function createConversationStore(deps) {
517
746
  return deps.durably(async (local) => {
518
747
  if (!replicated.has(conversation))
519
748
  return;
520
- const read = reads.get(conversation);
749
+ const read = (await readsOf(local, [conversation])).get(conversation)?.read;
521
750
  const advances = read !== undefined &&
522
751
  !behind.has(conversation) &&
523
752
  (read === null || message.position > read);
@@ -527,23 +756,22 @@ export function createConversationStore(deps) {
527
756
  messages: [message],
528
757
  ...(advances ? { read: { at: message.position, partial: false } } : {}),
529
758
  });
530
- stored.add(conversation);
531
- if (advances)
532
- reads.set(conversation, message.position);
533
759
  sealTurns(conversation, new Set([message.turn]));
534
760
  await slide(local, conversation, [message]);
535
761
  deps.publish(conversationChangeSet([conversation]));
762
+ tellList();
536
763
  });
537
764
  }
538
765
  async function readHistories(conversations, ended) {
539
766
  const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
767
+ const held = await readsOf(await deps.local(), conversations);
540
768
  let waiting = conversations.map((conversation) => ({
541
769
  conversation,
542
- after: reads.get(conversation) ?? undefined,
770
+ after: held.get(conversation)?.read ?? undefined,
543
771
  }));
544
772
  while (waiting.length > 0) {
545
773
  const asking = waiting.slice(0, HISTORY_CONVERSATIONS);
546
- const result = await deps.call('conversation.history', {
774
+ const result = await deps.history({
547
775
  conversations: asking.map(({ conversation, after }) => ({
548
776
  conversation,
549
777
  ...(after === undefined ? {} : { after }),
@@ -595,15 +823,10 @@ export function createConversationStore(deps) {
595
823
  return;
596
824
  const whole = next === undefined;
597
825
  const cursor = next ?? newestOf(arriving, from);
598
- const partial = !whole && (partials.has(conversation) || !reads.has(conversation));
826
+ const held = (await readsOf(local, [conversation])).get(conversation);
827
+ const partial = !whole && (held?.partial === true || held?.read === undefined);
599
828
  const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
600
829
  await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
601
- stored.add(conversation);
602
- reads.set(conversation, cursor);
603
- if (partial)
604
- partials.add(conversation);
605
- else
606
- partials.delete(conversation);
607
830
  sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
608
831
  if (whole && (gaps.get(conversation) ?? 0) === generation)
609
832
  behind.delete(conversation);
@@ -611,6 +834,8 @@ export function createConversationStore(deps) {
611
834
  if (arriving.length > 0 || sealing || whole) {
612
835
  deps.publish(conversationChangeSet([conversation]));
613
836
  }
837
+ if (arriving.length > 0)
838
+ tellSoon();
614
839
  });
615
840
  }
616
841
  function heard(event) {
@@ -646,6 +871,10 @@ export function createConversationStore(deps) {
646
871
  live(conversation, { starting: event.step });
647
872
  return;
648
873
  }
874
+ if (event.kind === 'subagents') {
875
+ live(conversation, { subagents: structuredClone(event.subagents) });
876
+ return;
877
+ }
649
878
  if (event.kind === 'link') {
650
879
  live(conversation, { link: { connected: event.connected } });
651
880
  return;
@@ -657,6 +886,7 @@ export function createConversationStore(deps) {
657
886
  const arriving = acceptedMessage(event.message);
658
887
  if (arriving === null)
659
888
  return;
889
+ noted(conversation, arriving);
660
890
  if (replicated.has(conversation)) {
661
891
  if (deps.full()) {
662
892
  gap(conversation);
@@ -672,17 +902,24 @@ export function createConversationStore(deps) {
672
902
  emit(conversation);
673
903
  }
674
904
  async function kept(local, conversation, following) {
675
- if (following === null || empty(following)) {
905
+ const shown = following === null || empty(following) ? null : following;
906
+ if (shown === null)
676
907
  await local.dropChatDraft(conversation);
677
- drafts.delete(conversation);
678
- }
679
- else {
680
- await local.keepChatDrafts([following]);
681
- drafts.set(conversation, following);
908
+ else
909
+ await local.keepChatDrafts([shown]);
910
+ shownDraft(conversation, shown);
911
+ }
912
+ function shownDraft(conversation, draft) {
913
+ for (const view of views.values()) {
914
+ if (view.conversation === conversation)
915
+ view.draft = draft;
682
916
  }
683
917
  }
918
+ async function draftAt(local, conversation) {
919
+ return (await local.chatDraft(conversation)) ?? undefined;
920
+ }
684
921
  async function sealed(local, conversation, turns) {
685
- const held = drafts.get(conversation);
922
+ const held = await draftAt(local, conversation);
686
923
  const submitted = held?.submitted ?? [];
687
924
  const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
688
925
  if (held === undefined || left.length === submitted.length)
@@ -693,8 +930,8 @@ export function createConversationStore(deps) {
693
930
  function moved(agent, operation, to) {
694
931
  const from = newChat(agent);
695
932
  return deps.durably(async (local) => {
696
- const held = drafts.get(from);
697
- const earlier = (held?.moves ?? []).filter((one) => !deps.expired(one));
933
+ const held = await draftAt(local, from);
934
+ const earlier = held?.moves ?? [];
698
935
  if (earlier.includes(operation))
699
936
  return;
700
937
  const blank = {
@@ -703,18 +940,20 @@ export function createConversationStore(deps) {
703
940
  revision: chatDraftRevision(),
704
941
  };
705
942
  const { moves: _answered, ...unsent } = held ?? blank;
706
- const taken = drafts.has(to) || empty(unsent) ? [] : [{ ...unsent, conversation: to }];
943
+ const taken = (await draftAt(local, to)) !== undefined || empty(unsent)
944
+ ? []
945
+ : [{ ...unsent, conversation: to }];
707
946
  const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
708
947
  await local.keepChatDrafts([left, ...taken]);
709
- drafts.set(from, left);
948
+ shownDraft(from, left);
710
949
  for (const one of taken)
711
- drafts.set(to, one);
950
+ shownDraft(to, one);
712
951
  deps.publish(conversationChangeSet([from, to]));
713
952
  });
714
953
  }
715
954
  function withDraft(conversation, next) {
716
955
  return deps.durably(async (local) => {
717
- const following = await next(drafts.get(conversation), local);
956
+ const following = await next(await draftAt(local, conversation), local);
718
957
  if (following === undefined)
719
958
  return;
720
959
  await kept(local, conversation, following);
@@ -723,10 +962,8 @@ export function createConversationStore(deps) {
723
962
  }
724
963
  async function submitted(conversation, operation, draft) {
725
964
  let submission;
726
- let earlier = false;
727
965
  await withDraft(conversation, (current) => {
728
966
  submission = current?.submitted?.find((one) => one.operation === operation);
729
- earlier = submission !== undefined;
730
967
  if (submission !== undefined)
731
968
  return undefined;
732
969
  const said = draft ?? current;
@@ -739,23 +976,15 @@ export function createConversationStore(deps) {
739
976
  submitted: [...(current?.submitted ?? []), submission],
740
977
  };
741
978
  });
742
- return { submission: submission, earlier };
743
- }
744
- function forgotten(conversation, submission) {
745
- return withDraft(conversation, (current) => {
746
- const held = current?.submitted ?? [];
747
- if (current === undefined || !held.includes(submission))
748
- return undefined;
749
- return withSubmitted(current, held.filter((one) => one !== submission));
750
- });
979
+ return submission;
751
980
  }
752
981
  async function dispatched(conversation, submission) {
753
982
  const dispatch = { answered: false };
754
983
  sending.set(submission.operation, dispatch);
755
984
  try {
756
- const result = await invoke('conversation.send', conversation, {
757
- text: submission.text,
985
+ const result = await invoke('conversations/send', conversation, {
758
986
  operation_id: submission.operation,
987
+ text: submission.text,
759
988
  });
760
989
  dispatch.answered = result.kind !== 'transport-uncertainty';
761
990
  if (result.kind === 'revoked-authorization')
@@ -764,7 +993,7 @@ export function createConversationStore(deps) {
764
993
  deps.unanswered();
765
994
  return result;
766
995
  }
767
- const turn = answered(result, 'turn');
996
+ const turn = answered(result, 'message_id');
768
997
  await withDraft(conversation, async (current, local) => {
769
998
  const held = current?.submitted?.find((one) => one.operation === submission.operation);
770
999
  if (current === undefined || held === undefined || held.turn !== null)
@@ -784,29 +1013,41 @@ export function createConversationStore(deps) {
784
1013
  sending.delete(submission.operation);
785
1014
  }
786
1015
  }
787
- function invoke(name, conversation, extra = {}) {
788
- return deps.call(name, { conversation, ...extra });
1016
+ function invoke(name, conversation, extra = { operation_id: changeBatchIdentity() }) {
1017
+ return deps.call(name, { conversation_id: conversation, ...extra });
789
1018
  }
790
1019
  const surface = {
791
- async list() {
792
- await agentsReady();
793
- return entries();
1020
+ list() {
1021
+ return new Promise((settle) => {
1022
+ const answer = (listed) => {
1023
+ unlisted.delete(answer);
1024
+ listWatchers.delete(answer);
1025
+ settle(listed);
1026
+ };
1027
+ unlisted.add(answer);
1028
+ learned();
1029
+ });
794
1030
  },
795
1031
  async read(conversation, options = {}) {
796
- const asked = askedOf(options);
1032
+ const { text, ...windowed } = options;
1033
+ const asked = askedOf(windowed);
1034
+ const phrase = phraseOf(text);
797
1035
  return deps.durably(async (local) => {
798
- const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
799
- const total = asked.total ? await local.messageCount(conversation) : null;
800
- return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total)));
1036
+ const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before, phrase);
1037
+ const total = asked.total ? await local.messageCount(conversation, phrase) : null;
1038
+ const marked = await local.jobMarkers(conversation);
1039
+ return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total), (await local.chatDraft(conversation)) ?? null, await endedIn(conversation), marked));
801
1040
  });
802
1041
  },
803
1042
  async create(input) {
804
- const result = await deps.call('conversation.create', input);
805
- const conversation = answered(result, 'conversation');
1043
+ const result = await deps.call('conversations/create', input);
1044
+ const conversation = answered(result, 'conversation_id');
806
1045
  if (conversation === null)
807
1046
  return result;
808
- if (!listing.has(conversation))
1047
+ const listed = (await heldList()) ?? [];
1048
+ if (!listed.some((entry) => entry.conversation === conversation)) {
809
1049
  creations.add(conversation);
1050
+ }
810
1051
  const creation = creationOf(input);
811
1052
  if (creation !== null)
812
1053
  await moved(creation.agent, creation.operation, conversation);
@@ -823,11 +1064,7 @@ export function createConversationStore(deps) {
823
1064
  });
824
1065
  if (held === null)
825
1066
  return DURABLE_WRITE;
826
- const { submission, earlier } = held;
827
- if (!earlier || !deps.expired(operation))
828
- return dispatched(conversation, submission);
829
- await forgotten(conversation, submission);
830
- return EXPIRED;
1067
+ return dispatched(conversation, held);
831
1068
  }
832
1069
  if (draft !== undefined) {
833
1070
  await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
@@ -836,38 +1073,20 @@ export function createConversationStore(deps) {
836
1073
  }
837
1074
  return OFFLINE;
838
1075
  },
839
- async attach(room, file) {
840
- if (!deps.online())
841
- return OFFLINE;
842
- return deps.call('conversation.attach', {
843
- room,
844
- ...(await declaredFile(file)),
845
- blob: file.blob,
846
- });
847
- },
848
1076
  answer(conversation, answer) {
849
- return invoke('conversation.answer', conversation, { answer });
1077
+ return invoke('conversations/interaction/answer', conversation, {
1078
+ operation_id: changeBatchIdentity(),
1079
+ ...answer,
1080
+ });
850
1081
  },
851
1082
  interrupt(conversation) {
852
- return invoke('conversation.interrupt', conversation);
1083
+ return invoke('conversations/interrupt', conversation);
853
1084
  },
854
1085
  kill(conversation) {
855
- return invoke('conversation.kill', conversation);
856
- },
857
- async cancel(conversation, turn) {
858
- const result = await invoke('conversation.cancel', conversation, { turn });
859
- if (result.kind !== 'action')
860
- return result;
861
- await withDraft(conversation, (current) => {
862
- const held = current?.submitted?.find((one) => one.turn === turn);
863
- if (current === undefined || held === undefined)
864
- return undefined;
865
- return returned(current, held, current.submitted.filter((one) => one !== held));
866
- });
867
- return result;
1086
+ return invoke('conversations/kill', conversation);
868
1087
  },
869
1088
  delete(conversation) {
870
- return invoke('conversation.delete', conversation);
1089
+ return invoke('conversations/delete', conversation);
871
1090
  },
872
1091
  };
873
1092
  const chatDraft = {
@@ -896,33 +1115,40 @@ export function createConversationStore(deps) {
896
1115
  return {
897
1116
  surface,
898
1117
  chatDraft,
899
- hydrate(held, kept) {
900
- stored.clear();
901
- reads.clear();
902
- partials.clear();
903
- for (const entry of held) {
904
- stored.add(entry.conversation);
905
- if (entry.read !== undefined)
906
- reads.set(entry.conversation, entry.read);
907
- if (entry.partial)
908
- partials.add(entry.conversation);
909
- }
910
- drafts.clear();
911
- for (const record of kept)
912
- drafts.set(record.conversation, record);
1118
+ async opened(local) {
1119
+ const { conversationList, conversationCursor } = await local.facts();
1120
+ if (conversationList === null || listKnown)
1121
+ return;
1122
+ deps.stream()?.hold(conversationCursor);
1123
+ learned();
913
1124
  },
914
1125
  async reconcile() {
915
1126
  if (!listKnown)
916
1127
  return;
917
- const named = new Set(listing.keys());
1128
+ const local = await deps.local();
1129
+ const [held, kept, { conversationList }] = await Promise.all([
1130
+ local.conversations(),
1131
+ local.chatDrafts(),
1132
+ local.facts(),
1133
+ ]);
1134
+ const list = conversationList ?? [];
1135
+ const entries = new Map(list.map((entry) => [entry.conversation, entry]));
1136
+ const named = new Set(entries.keys());
1137
+ const reads = new Map(held.map((one) => [one.conversation, one]));
918
1138
  const leaving = [
919
- ...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
1139
+ ...new Set([
1140
+ ...replicated,
1141
+ ...reads.keys(),
1142
+ ...kept.map(({ conversation }) => conversation),
1143
+ ]),
920
1144
  ].filter((conversation) => !named.has(conversation) &&
921
1145
  !creations.has(conversation) &&
922
- (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
1146
+ (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation, list)));
923
1147
  for (const conversation of named) {
924
- if (!replicated.has(conversation))
1148
+ if (!replicated.has(conversation) &&
1149
+ missed(conversation, reads.get(conversation), entries.get(conversation))) {
925
1150
  behind.add(conversation);
1151
+ }
926
1152
  }
927
1153
  replicated = named;
928
1154
  handReplicated();
@@ -953,16 +1179,16 @@ export function createConversationStore(deps) {
953
1179
  progress.complete = false;
954
1180
  },
955
1181
  async resend(progress) {
956
- for (const [conversation, held] of [...drafts]) {
957
- for (const submission of held.submitted ?? []) {
958
- if (submission.turn !== null || sending.has(submission.operation))
959
- continue;
1182
+ const local = await deps.local();
1183
+ const tried = new Set();
1184
+ for (const { conversation } of await local.chatDrafts()) {
1185
+ for (;;) {
1186
+ const submission = (await local.chatDraft(conversation))?.submitted?.find((one) => one.turn === null && !sending.has(one.operation) && !tried.has(one.operation));
1187
+ if (submission === undefined)
1188
+ break;
960
1189
  if (deps.closed())
961
1190
  return;
962
- if (deps.expired(submission.operation)) {
963
- await forgotten(conversation, submission);
964
- continue;
965
- }
1191
+ tried.add(submission.operation);
966
1192
  const result = await dispatched(conversation, submission);
967
1193
  if (result.kind === 'revoked-authorization')
968
1194
  return;
@@ -983,11 +1209,8 @@ export function createConversationStore(deps) {
983
1209
  known() {
984
1210
  return listKnown;
985
1211
  },
986
- reconnected() {
987
- for (const conversation of replicated)
988
- gap(conversation);
989
- },
990
1212
  reopened() {
1213
+ relisting = null;
991
1214
  for (const [operation, dispatch] of sending)
992
1215
  if (!dispatch.answered)
993
1216
  sending.delete(operation);
@@ -1003,30 +1226,37 @@ export function createConversationStore(deps) {
1003
1226
  asked,
1004
1227
  watchers: new Set(),
1005
1228
  window: null,
1229
+ draft: null,
1230
+ ended: null,
1231
+ marked: [],
1006
1232
  more: false,
1007
1233
  total: null,
1008
1234
  };
1009
1235
  views.set(key, view);
1010
1236
  const watcher = { notify: deliver, ready: false };
1011
1237
  view.watchers.add(watcher);
1012
- void deps
1013
- .durably(async (local) => {
1238
+ const reading = new AbortController();
1239
+ void untilAnswered(() => deps.durably(async (local) => {
1014
1240
  if (view.window !== null)
1015
1241
  return;
1016
1242
  const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
1243
+ view.draft = (await local.chatDraft(conversation)) ?? null;
1244
+ view.ended = await endedIn(conversation);
1017
1245
  view.window = messages;
1246
+ view.marked = await local.jobMarkers(conversation);
1018
1247
  view.more = more;
1019
1248
  view.total = asked.total ? await local.messageCount(conversation) : null;
1020
- })
1249
+ }), reading.signal)
1021
1250
  .then(() => {
1022
1251
  if (deps.closed() || !view.watchers.has(watcher))
1023
1252
  return;
1024
1253
  watcher.ready = true;
1025
1254
  const window = view.window ?? [];
1026
- watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
1255
+ watcher.notify(viewOf(conversation, window, sharedFrame(view, window), view.draft, view.ended, view.marked));
1027
1256
  })
1028
1257
  .catch(() => { });
1029
1258
  return () => {
1259
+ reading.abort();
1030
1260
  if (!view.watchers.delete(watcher))
1031
1261
  return;
1032
1262
  if (view.watchers.size === 0)
@@ -1034,77 +1264,73 @@ export function createConversationStore(deps) {
1034
1264
  };
1035
1265
  },
1036
1266
  watchList(deliver) {
1037
- listWatchers.add(deliver);
1038
- void agentsReady().then(() => {
1039
- if (!deps.closed() && listWatchers.has(deliver))
1040
- deliver(entries());
1041
- });
1267
+ unlisted.add(deliver);
1268
+ learned();
1042
1269
  return () => {
1270
+ unlisted.delete(deliver);
1043
1271
  listWatchers.delete(deliver);
1044
1272
  };
1045
1273
  },
1046
1274
  watchAgents(deliver) {
1047
- agentWatchers.add(deliver);
1048
- void agentsReady().then(() => {
1049
- if (!deps.closed() && agentWatchers.has(deliver))
1050
- deliver(agents());
1051
- });
1275
+ unnamed.add(deliver);
1276
+ learned();
1277
+ readAgents();
1052
1278
  return () => {
1279
+ unnamed.delete(deliver);
1053
1280
  agentWatchers.delete(deliver);
1054
1281
  };
1055
1282
  },
1283
+ learned,
1056
1284
  changed(set) {
1057
1285
  for (const conversation of set.conversations)
1058
1286
  emit(conversation);
1059
- const declaration = deps.agents();
1060
- if (declaration !== undefined &&
1061
- set.documents.some((change) => change.locator === declaration.locator)) {
1062
- agentsRead = readAgents().catch(() => {
1063
- agentsRead = null;
1064
- });
1065
- }
1066
1287
  },
1067
1288
  receive(event) {
1068
1289
  if (deps.closed())
1069
- return false;
1070
- if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
1071
- if (event.kind === 'list') {
1072
- listing.clear();
1073
- for (const entry of event.entries) {
1074
- listing.set(entry.conversation, { ...entry });
1075
- creations.delete(entry.conversation);
1076
- }
1077
- listKnown = true;
1078
- openingEnded();
1079
- }
1080
- else if (event.kind === 'listed') {
1081
- listing.set(event.entry.conversation, { ...event.entry });
1082
- creations.delete(event.entry.conversation);
1083
- }
1084
- else {
1085
- listing.delete(event.conversation);
1290
+ return;
1291
+ if (event.kind === 'list') {
1292
+ if (event.whole)
1293
+ relisting ??= new Map();
1294
+ if (relisting !== null) {
1295
+ for (const entry of event.entries)
1296
+ relisting.set(entry.conversation, { ...entry });
1086
1297
  }
1087
- emitList();
1088
- return (event.kind !== 'list' &&
1089
- listKnown &&
1090
- (listing.size !== replicated.size ||
1091
- [...listing.keys()].some((conversation) => !replicated.has(conversation))));
1298
+ if (event.more && relisting !== null)
1299
+ return;
1300
+ const whole = relisting;
1301
+ relisting = null;
1302
+ listChanged({
1303
+ replacing: whole !== null,
1304
+ set: whole === null ? event.entries : [...whole.values()],
1305
+ gone: null,
1306
+ ...(event.more ? { paged: true } : { cursor: event.cursor }),
1307
+ });
1308
+ return;
1309
+ }
1310
+ if (event.kind === 'listed') {
1311
+ relisting?.set(event.entry.conversation, { ...event.entry });
1312
+ listChanged({ replacing: false, set: [event.entry], gone: null });
1313
+ return;
1314
+ }
1315
+ if (event.kind === 'unlisted') {
1316
+ relisting?.delete(event.conversation);
1317
+ listChanged({ replacing: false, set: [], gone: event.conversation });
1318
+ return;
1092
1319
  }
1093
1320
  heard(event);
1094
- return false;
1095
- },
1096
- searchable() {
1097
- return [...(listKnown ? listing.keys() : stored)].sort();
1098
- },
1099
- acquired(conversation) {
1100
- return reads.has(conversation) && !partials.has(conversation);
1101
1321
  },
1102
1322
  close() {
1323
+ closing.abort();
1324
+ if (rowTimer !== null)
1325
+ clearTimeout(rowTimer);
1326
+ rowTimer = null;
1103
1327
  views.clear();
1104
1328
  lives.clear();
1105
1329
  unsent.clear();
1106
1330
  listWatchers.clear();
1331
+ unlisted.clear();
1107
1332
  agentWatchers.clear();
1333
+ unnamed.clear();
1108
1334
  },
1109
1335
  };
1110
1336
  }