@agentshouse/mdruntime 0.1.0-c0bed82db8d73 → 0.1.0-c0c1e81da9166

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 (174) hide show
  1. package/README.md +3 -2
  2. package/dist/adapter.d.ts +101 -71
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/attached-model.d.ts +4 -10
  5. package/dist/attached-model.d.ts.map +1 -1
  6. package/dist/attached-model.js +35 -46
  7. package/dist/bootstrap.d.ts +16 -15
  8. package/dist/bootstrap.d.ts.map +1 -1
  9. package/dist/bootstrap.js +273 -131
  10. package/dist/chain.d.ts +2 -1
  11. package/dist/chain.d.ts.map +1 -1
  12. package/dist/change.d.ts +1 -0
  13. package/dist/change.d.ts.map +1 -1
  14. package/dist/chat-types.d.ts +3 -0
  15. package/dist/chat-types.d.ts.map +1 -0
  16. package/dist/chat-types.js +2 -0
  17. package/dist/client.d.ts +15 -9
  18. package/dist/client.d.ts.map +1 -1
  19. package/dist/client.js +54 -6
  20. package/dist/collection.d.ts +7 -4
  21. package/dist/collection.d.ts.map +1 -1
  22. package/dist/collection.js +24 -19
  23. package/dist/compile.d.ts +6 -6
  24. package/dist/compile.d.ts.map +1 -1
  25. package/dist/compile.js +278 -282
  26. package/dist/connection.d.ts +16 -11
  27. package/dist/connection.d.ts.map +1 -1
  28. package/dist/connection.js +2312 -1988
  29. package/dist/context.d.ts +2 -0
  30. package/dist/context.d.ts.map +1 -0
  31. package/dist/context.js +7 -0
  32. package/dist/conversation-row.d.ts +33 -0
  33. package/dist/conversation-row.d.ts.map +1 -0
  34. package/dist/conversation-row.js +47 -0
  35. package/dist/conversation.d.ts +27 -18
  36. package/dist/conversation.d.ts.map +1 -1
  37. package/dist/conversation.js +489 -260
  38. package/dist/durability.d.ts +6 -1
  39. package/dist/durability.d.ts.map +1 -1
  40. package/dist/entry.d.ts +3 -2
  41. package/dist/entry.d.ts.map +1 -1
  42. package/dist/entry.js +18 -8
  43. package/dist/figures.d.ts +48 -0
  44. package/dist/figures.d.ts.map +1 -0
  45. package/dist/figures.js +197 -0
  46. package/dist/file.d.ts +2 -11
  47. package/dist/file.d.ts.map +1 -1
  48. package/dist/file.js +6 -4
  49. package/dist/host.d.ts +3 -7
  50. package/dist/host.d.ts.map +1 -1
  51. package/dist/host.js +26 -29
  52. package/dist/identity.d.ts +0 -1
  53. package/dist/identity.d.ts.map +1 -1
  54. package/dist/identity.js +0 -3
  55. package/dist/index.d.ts +16 -9
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +6 -1
  58. package/dist/job.d.ts +33 -0
  59. package/dist/job.d.ts.map +1 -0
  60. package/dist/job.js +54 -0
  61. package/dist/live.d.ts +5 -4
  62. package/dist/live.d.ts.map +1 -1
  63. package/dist/live.js +62 -20
  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 +13 -6
  68. package/dist/memory.d.ts.map +1 -1
  69. package/dist/memory.js +218 -91
  70. package/dist/messages.d.ts +27 -3
  71. package/dist/messages.d.ts.map +1 -1
  72. package/dist/messages.js +97 -19
  73. package/dist/model.d.ts +8 -10
  74. package/dist/model.d.ts.map +1 -1
  75. package/dist/model.js +6 -18
  76. package/dist/observation.d.ts +3 -2
  77. package/dist/observation.d.ts.map +1 -1
  78. package/dist/observation.js +8 -3
  79. package/dist/outcome.d.ts +13 -5
  80. package/dist/outcome.d.ts.map +1 -1
  81. package/dist/outcome.js +16 -2
  82. package/dist/overlay.d.ts +1 -0
  83. package/dist/overlay.d.ts.map +1 -1
  84. package/dist/overlay.js +11 -0
  85. package/dist/parts.d.ts +59 -0
  86. package/dist/parts.d.ts.map +1 -0
  87. package/dist/parts.js +118 -0
  88. package/dist/paths.d.ts +1 -0
  89. package/dist/paths.d.ts.map +1 -1
  90. package/dist/paths.js +4 -0
  91. package/dist/query.d.ts +18 -207
  92. package/dist/query.d.ts.map +1 -1
  93. package/dist/query.js +3 -505
  94. package/dist/questions.d.ts +32 -0
  95. package/dist/questions.d.ts.map +1 -0
  96. package/dist/questions.js +118 -0
  97. package/dist/reach.d.ts +18 -11
  98. package/dist/reach.d.ts.map +1 -1
  99. package/dist/reach.js +201 -126
  100. package/dist/react.d.ts +3 -1
  101. package/dist/react.d.ts.map +1 -1
  102. package/dist/read.d.ts +7 -0
  103. package/dist/read.d.ts.map +1 -0
  104. package/dist/read.js +17 -0
  105. package/dist/readiness.d.ts +3 -17
  106. package/dist/readiness.d.ts.map +1 -1
  107. package/dist/readiness.js +4 -10
  108. package/dist/recovery.d.ts +1 -0
  109. package/dist/recovery.d.ts.map +1 -1
  110. package/dist/recovery.js +35 -1
  111. package/dist/refresh.d.ts +7 -5
  112. package/dist/refresh.d.ts.map +1 -1
  113. package/dist/refresh.js +34 -10
  114. package/dist/replica.d.ts +10 -9
  115. package/dist/replica.d.ts.map +1 -1
  116. package/dist/replica.js +53 -21
  117. package/dist/room-draft.d.ts +21 -5
  118. package/dist/room-draft.d.ts.map +1 -1
  119. package/dist/room-draft.js +98 -51
  120. package/dist/rows.d.ts +18 -16
  121. package/dist/rows.d.ts.map +1 -1
  122. package/dist/rows.js +45 -153
  123. package/dist/save.d.ts +2 -0
  124. package/dist/save.d.ts.map +1 -1
  125. package/dist/save.js +22 -13
  126. package/dist/search.d.ts +12 -63
  127. package/dist/search.d.ts.map +1 -1
  128. package/dist/search.js +9 -52
  129. package/dist/shared.d.ts +0 -1
  130. package/dist/shared.d.ts.map +1 -1
  131. package/dist/shared.js +0 -10
  132. package/dist/source.d.ts +61 -0
  133. package/dist/source.d.ts.map +1 -0
  134. package/dist/source.js +81 -0
  135. package/dist/status.d.ts +4 -5
  136. package/dist/status.d.ts.map +1 -1
  137. package/dist/status.js +3 -7
  138. package/dist/store.d.ts +104 -62
  139. package/dist/store.d.ts.map +1 -1
  140. package/dist/store.js +159 -161
  141. package/dist/stored.d.ts +66 -55
  142. package/dist/stored.d.ts.map +1 -1
  143. package/dist/stored.js +343 -273
  144. package/dist/tables.d.ts +110 -65
  145. package/dist/tables.d.ts.map +1 -1
  146. package/dist/tables.js +188 -99
  147. package/dist/tallies.d.ts +18 -0
  148. package/dist/tallies.d.ts.map +1 -0
  149. package/dist/tallies.js +129 -0
  150. package/dist/transfer.d.ts +0 -5
  151. package/dist/transfer.d.ts.map +1 -1
  152. package/dist/validation.d.ts +3 -4
  153. package/dist/validation.d.ts.map +1 -1
  154. package/dist/validation.js +42 -100
  155. package/dist/windows.d.ts +1 -12
  156. package/dist/windows.d.ts.map +1 -1
  157. package/dist/windows.js +2 -75
  158. package/dist/wire.d.ts +18 -8
  159. package/dist/wire.d.ts.map +1 -1
  160. package/dist/wire.js +6 -2
  161. package/package.json +2 -3
  162. package/AGENTS.md +0 -312
  163. package/dist/fence.d.ts +0 -6
  164. package/dist/fence.d.ts.map +0 -1
  165. package/dist/fence.js +0 -10
  166. package/dist/fulltext.d.ts +0 -27
  167. package/dist/fulltext.d.ts.map +0 -1
  168. package/dist/fulltext.js +0 -368
  169. package/dist/reads.d.ts +0 -11
  170. package/dist/reads.d.ts.map +0 -1
  171. package/dist/reads.js +0 -100
  172. package/dist/words.d.ts +0 -19
  173. package/dist/words.d.ts.map +0 -1
  174. 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,31 +976,24 @@ 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
- const dispatch = {};
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
  });
989
+ dispatch.answered = result.kind !== 'transport-uncertainty';
760
990
  if (result.kind === 'revoked-authorization')
761
991
  return result;
762
992
  if (result.kind !== 'action' && result.retryable) {
763
993
  deps.unanswered();
764
994
  return result;
765
995
  }
766
- const turn = answered(result, 'turn');
996
+ const turn = answered(result, 'message_id');
767
997
  await withDraft(conversation, async (current, local) => {
768
998
  const held = current?.submitted?.find((one) => one.operation === submission.operation);
769
999
  if (current === undefined || held === undefined || held.turn !== null)
@@ -783,29 +1013,41 @@ export function createConversationStore(deps) {
783
1013
  sending.delete(submission.operation);
784
1014
  }
785
1015
  }
786
- function invoke(name, conversation, extra = {}) {
787
- return deps.call(name, { conversation, ...extra });
1016
+ function invoke(name, conversation, extra = { operation_id: changeBatchIdentity() }) {
1017
+ return deps.call(name, { conversation_id: conversation, ...extra });
788
1018
  }
789
1019
  const surface = {
790
- async list() {
791
- await agentsReady();
792
- 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
+ });
793
1030
  },
794
1031
  async read(conversation, options = {}) {
795
- const asked = askedOf(options);
1032
+ const { text, ...windowed } = options;
1033
+ const asked = askedOf(windowed);
1034
+ const phrase = phraseOf(text);
796
1035
  return deps.durably(async (local) => {
797
- const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
798
- const total = asked.total ? await local.messageCount(conversation) : null;
799
- 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));
800
1040
  });
801
1041
  },
802
1042
  async create(input) {
803
- const result = await deps.call('conversation.create', input);
804
- const conversation = answered(result, 'conversation');
1043
+ const result = await deps.call('conversations/create', input);
1044
+ const conversation = answered(result, 'conversation_id');
805
1045
  if (conversation === null)
806
1046
  return result;
807
- if (!listing.has(conversation))
1047
+ const listed = (await heldList()) ?? [];
1048
+ if (!listed.some((entry) => entry.conversation === conversation)) {
808
1049
  creations.add(conversation);
1050
+ }
809
1051
  const creation = creationOf(input);
810
1052
  if (creation !== null)
811
1053
  await moved(creation.agent, creation.operation, conversation);
@@ -822,11 +1064,7 @@ export function createConversationStore(deps) {
822
1064
  });
823
1065
  if (held === null)
824
1066
  return DURABLE_WRITE;
825
- const { submission, earlier } = held;
826
- if (!earlier || !deps.expired(operation))
827
- return dispatched(conversation, submission);
828
- await forgotten(conversation, submission);
829
- return EXPIRED;
1067
+ return dispatched(conversation, held);
830
1068
  }
831
1069
  if (draft !== undefined) {
832
1070
  await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
@@ -835,38 +1073,20 @@ export function createConversationStore(deps) {
835
1073
  }
836
1074
  return OFFLINE;
837
1075
  },
838
- async attach(room, file) {
839
- if (!deps.online())
840
- return OFFLINE;
841
- return deps.call('conversation.attach', {
842
- room,
843
- ...(await declaredFile(file)),
844
- blob: file.blob,
845
- });
846
- },
847
1076
  answer(conversation, answer) {
848
- return invoke('conversation.answer', conversation, { answer });
1077
+ return invoke('conversations/interaction/answer', conversation, {
1078
+ operation_id: changeBatchIdentity(),
1079
+ ...answer,
1080
+ });
849
1081
  },
850
1082
  interrupt(conversation) {
851
- return invoke('conversation.interrupt', conversation);
1083
+ return invoke('conversations/interrupt', conversation);
852
1084
  },
853
1085
  kill(conversation) {
854
- return invoke('conversation.kill', conversation);
855
- },
856
- async cancel(conversation, turn) {
857
- const result = await invoke('conversation.cancel', conversation, { turn });
858
- if (result.kind !== 'action')
859
- return result;
860
- await withDraft(conversation, (current) => {
861
- const held = current?.submitted?.find((one) => one.turn === turn);
862
- if (current === undefined || held === undefined)
863
- return undefined;
864
- return returned(current, held, current.submitted.filter((one) => one !== held));
865
- });
866
- return result;
1086
+ return invoke('conversations/kill', conversation);
867
1087
  },
868
1088
  delete(conversation) {
869
- return invoke('conversation.delete', conversation);
1089
+ return invoke('conversations/delete', conversation);
870
1090
  },
871
1091
  };
872
1092
  const chatDraft = {
@@ -895,33 +1115,40 @@ export function createConversationStore(deps) {
895
1115
  return {
896
1116
  surface,
897
1117
  chatDraft,
898
- hydrate(held, kept) {
899
- stored.clear();
900
- reads.clear();
901
- partials.clear();
902
- for (const entry of held) {
903
- stored.add(entry.conversation);
904
- if (entry.read !== undefined)
905
- reads.set(entry.conversation, entry.read);
906
- if (entry.partial)
907
- partials.add(entry.conversation);
908
- }
909
- drafts.clear();
910
- for (const record of kept)
911
- 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();
912
1124
  },
913
1125
  async reconcile() {
914
1126
  if (!listKnown)
915
1127
  return;
916
- 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]));
917
1138
  const leaving = [
918
- ...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
1139
+ ...new Set([
1140
+ ...replicated,
1141
+ ...reads.keys(),
1142
+ ...kept.map(({ conversation }) => conversation),
1143
+ ]),
919
1144
  ].filter((conversation) => !named.has(conversation) &&
920
1145
  !creations.has(conversation) &&
921
- (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
1146
+ (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation, list)));
922
1147
  for (const conversation of named) {
923
- if (!replicated.has(conversation))
1148
+ if (!replicated.has(conversation) &&
1149
+ missed(conversation, reads.get(conversation), entries.get(conversation))) {
924
1150
  behind.add(conversation);
1151
+ }
925
1152
  }
926
1153
  replicated = named;
927
1154
  handReplicated();
@@ -952,16 +1179,16 @@ export function createConversationStore(deps) {
952
1179
  progress.complete = false;
953
1180
  },
954
1181
  async resend(progress) {
955
- for (const [conversation, held] of [...drafts]) {
956
- for (const submission of held.submitted ?? []) {
957
- if (submission.turn !== null || sending.has(submission.operation))
958
- 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;
959
1189
  if (deps.closed())
960
1190
  return;
961
- if (deps.expired(submission.operation)) {
962
- await forgotten(conversation, submission);
963
- continue;
964
- }
1191
+ tried.add(submission.operation);
965
1192
  const result = await dispatched(conversation, submission);
966
1193
  if (result.kind === 'revoked-authorization')
967
1194
  return;
@@ -982,12 +1209,11 @@ export function createConversationStore(deps) {
982
1209
  known() {
983
1210
  return listKnown;
984
1211
  },
985
- reconnected() {
986
- for (const conversation of replicated)
987
- gap(conversation);
988
- },
989
1212
  reopened() {
990
- sending.clear();
1213
+ relisting = null;
1214
+ for (const [operation, dispatch] of sending)
1215
+ if (!dispatch.answered)
1216
+ sending.delete(operation);
991
1217
  },
992
1218
  lost() {
993
1219
  catching.clear();
@@ -1000,30 +1226,37 @@ export function createConversationStore(deps) {
1000
1226
  asked,
1001
1227
  watchers: new Set(),
1002
1228
  window: null,
1229
+ draft: null,
1230
+ ended: null,
1231
+ marked: [],
1003
1232
  more: false,
1004
1233
  total: null,
1005
1234
  };
1006
1235
  views.set(key, view);
1007
1236
  const watcher = { notify: deliver, ready: false };
1008
1237
  view.watchers.add(watcher);
1009
- void deps
1010
- .durably(async (local) => {
1238
+ const reading = new AbortController();
1239
+ void untilAnswered(() => deps.durably(async (local) => {
1011
1240
  if (view.window !== null)
1012
1241
  return;
1013
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);
1014
1245
  view.window = messages;
1246
+ view.marked = await local.jobMarkers(conversation);
1015
1247
  view.more = more;
1016
1248
  view.total = asked.total ? await local.messageCount(conversation) : null;
1017
- })
1249
+ }), reading.signal)
1018
1250
  .then(() => {
1019
1251
  if (deps.closed() || !view.watchers.has(watcher))
1020
1252
  return;
1021
1253
  watcher.ready = true;
1022
1254
  const window = view.window ?? [];
1023
- watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
1255
+ watcher.notify(viewOf(conversation, window, sharedFrame(view, window), view.draft, view.ended, view.marked));
1024
1256
  })
1025
1257
  .catch(() => { });
1026
1258
  return () => {
1259
+ reading.abort();
1027
1260
  if (!view.watchers.delete(watcher))
1028
1261
  return;
1029
1262
  if (view.watchers.size === 0)
@@ -1031,77 +1264,73 @@ export function createConversationStore(deps) {
1031
1264
  };
1032
1265
  },
1033
1266
  watchList(deliver) {
1034
- listWatchers.add(deliver);
1035
- void agentsReady().then(() => {
1036
- if (!deps.closed() && listWatchers.has(deliver))
1037
- deliver(entries());
1038
- });
1267
+ unlisted.add(deliver);
1268
+ learned();
1039
1269
  return () => {
1270
+ unlisted.delete(deliver);
1040
1271
  listWatchers.delete(deliver);
1041
1272
  };
1042
1273
  },
1043
1274
  watchAgents(deliver) {
1044
- agentWatchers.add(deliver);
1045
- void agentsReady().then(() => {
1046
- if (!deps.closed() && agentWatchers.has(deliver))
1047
- deliver(agents());
1048
- });
1275
+ unnamed.add(deliver);
1276
+ learned();
1277
+ readAgents();
1049
1278
  return () => {
1279
+ unnamed.delete(deliver);
1050
1280
  agentWatchers.delete(deliver);
1051
1281
  };
1052
1282
  },
1283
+ learned,
1053
1284
  changed(set) {
1054
1285
  for (const conversation of set.conversations)
1055
1286
  emit(conversation);
1056
- const declaration = deps.agents();
1057
- if (declaration !== undefined &&
1058
- set.documents.some((change) => change.locator === declaration.locator)) {
1059
- agentsRead = readAgents().catch(() => {
1060
- agentsRead = null;
1061
- });
1062
- }
1063
1287
  },
1064
1288
  receive(event) {
1065
1289
  if (deps.closed())
1066
- return false;
1067
- if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
1068
- if (event.kind === 'list') {
1069
- listing.clear();
1070
- for (const entry of event.entries) {
1071
- listing.set(entry.conversation, { ...entry });
1072
- creations.delete(entry.conversation);
1073
- }
1074
- listKnown = true;
1075
- openingEnded();
1076
- }
1077
- else if (event.kind === 'listed') {
1078
- listing.set(event.entry.conversation, { ...event.entry });
1079
- creations.delete(event.entry.conversation);
1080
- }
1081
- else {
1082
- 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 });
1083
1297
  }
1084
- emitList();
1085
- return (event.kind !== 'list' &&
1086
- listKnown &&
1087
- (listing.size !== replicated.size ||
1088
- [...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;
1089
1319
  }
1090
1320
  heard(event);
1091
- return false;
1092
- },
1093
- searchable() {
1094
- return [...(listKnown ? listing.keys() : stored)].sort();
1095
- },
1096
- acquired(conversation) {
1097
- return reads.has(conversation) && !partials.has(conversation);
1098
1321
  },
1099
1322
  close() {
1323
+ closing.abort();
1324
+ if (rowTimer !== null)
1325
+ clearTimeout(rowTimer);
1326
+ rowTimer = null;
1100
1327
  views.clear();
1101
1328
  lives.clear();
1102
1329
  unsent.clear();
1103
1330
  listWatchers.clear();
1331
+ unlisted.clear();
1104
1332
  agentWatchers.clear();
1333
+ unnamed.clear();
1105
1334
  },
1106
1335
  };
1107
1336
  }