@agentshouse/mdruntime 0.1.0-cac707d698ca2 → 0.1.0-cad267257ceaf

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/README.md +1 -1
  2. package/dist/acceptance.d.ts +6 -0
  3. package/dist/acceptance.d.ts.map +1 -0
  4. package/dist/acceptance.js +389 -0
  5. package/dist/actions.d.ts +20 -0
  6. package/dist/actions.d.ts.map +1 -0
  7. package/dist/actions.js +250 -0
  8. package/dist/adapter.d.ts +17 -16
  9. package/dist/adapter.d.ts.map +1 -1
  10. package/dist/bootstrap.d.ts +8 -4
  11. package/dist/bootstrap.d.ts.map +1 -1
  12. package/dist/bootstrap.js +206 -76
  13. package/dist/cell.d.ts +6 -0
  14. package/dist/cell.d.ts.map +1 -0
  15. package/dist/cell.js +9 -0
  16. package/dist/change.d.ts +1 -0
  17. package/dist/change.d.ts.map +1 -1
  18. package/dist/chat-types.d.ts +5 -0
  19. package/dist/chat-types.d.ts.map +1 -0
  20. package/dist/chat-types.js +13 -0
  21. package/dist/client.d.ts +12 -1
  22. package/dist/client.d.ts.map +1 -1
  23. package/dist/client.js +1 -0
  24. package/dist/compile.d.ts +5 -0
  25. package/dist/compile.d.ts.map +1 -1
  26. package/dist/compile.js +186 -55
  27. package/dist/connection.d.ts +5 -143
  28. package/dist/connection.d.ts.map +1 -1
  29. package/dist/connection.js +98 -5016
  30. package/dist/conversation-row.d.ts +33 -0
  31. package/dist/conversation-row.d.ts.map +1 -0
  32. package/dist/conversation-row.js +53 -0
  33. package/dist/conversation.d.ts +15 -5
  34. package/dist/conversation.d.ts.map +1 -1
  35. package/dist/conversation.js +358 -193
  36. package/dist/discovery.d.ts +10 -0
  37. package/dist/discovery.d.ts.map +1 -0
  38. package/dist/discovery.js +175 -0
  39. package/dist/durability.d.ts +9 -0
  40. package/dist/durability.d.ts.map +1 -1
  41. package/dist/entry.d.ts.map +1 -1
  42. package/dist/entry.js +1 -0
  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/holdings.d.ts +29 -0
  47. package/dist/holdings.d.ts.map +1 -0
  48. package/dist/holdings.js +95 -0
  49. package/dist/host.d.ts +1 -0
  50. package/dist/host.d.ts.map +1 -1
  51. package/dist/host.js +7 -4
  52. package/dist/index.d.ts +18 -6
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +3 -1
  55. package/dist/install.d.ts +56 -0
  56. package/dist/install.d.ts.map +1 -0
  57. package/dist/install.js +560 -0
  58. package/dist/job.d.ts +20 -1
  59. package/dist/job.d.ts.map +1 -1
  60. package/dist/job.js +26 -0
  61. package/dist/lifecycle.d.ts +7 -0
  62. package/dist/lifecycle.d.ts.map +1 -0
  63. package/dist/lifecycle.js +267 -0
  64. package/dist/listing.d.ts +53 -0
  65. package/dist/listing.d.ts.map +1 -0
  66. package/dist/listing.js +175 -0
  67. package/dist/live.d.ts +4 -4
  68. package/dist/live.d.ts.map +1 -1
  69. package/dist/live.js +53 -15
  70. package/dist/locks.d.ts +9 -0
  71. package/dist/locks.d.ts.map +1 -0
  72. package/dist/locks.js +61 -0
  73. package/dist/log.d.ts +1 -0
  74. package/dist/log.d.ts.map +1 -1
  75. package/dist/lost.d.ts +5 -0
  76. package/dist/lost.d.ts.map +1 -0
  77. package/dist/lost.js +61 -0
  78. package/dist/memory.d.ts +1 -0
  79. package/dist/memory.d.ts.map +1 -1
  80. package/dist/memory.js +13 -5
  81. package/dist/messages.d.ts +25 -1
  82. package/dist/messages.d.ts.map +1 -1
  83. package/dist/messages.js +82 -3
  84. package/dist/model.d.ts +0 -2
  85. package/dist/model.d.ts.map +1 -1
  86. package/dist/model.js +0 -6
  87. package/dist/models.d.ts +14 -0
  88. package/dist/models.d.ts.map +1 -0
  89. package/dist/models.js +193 -0
  90. package/dist/opening.d.ts +4 -0
  91. package/dist/opening.d.ts.map +1 -0
  92. package/dist/opening.js +56 -0
  93. package/dist/outcome.d.ts +4 -1
  94. package/dist/outcome.d.ts.map +1 -1
  95. package/dist/outcome.js +3 -0
  96. package/dist/overlay.d.ts +1 -0
  97. package/dist/overlay.d.ts.map +1 -1
  98. package/dist/overlay.js +11 -0
  99. package/dist/parts.d.ts +59 -0
  100. package/dist/parts.d.ts.map +1 -0
  101. package/dist/parts.js +118 -0
  102. package/dist/preparation.d.ts +20 -0
  103. package/dist/preparation.d.ts.map +1 -0
  104. package/dist/preparation.js +226 -0
  105. package/dist/queries.d.ts +16 -0
  106. package/dist/queries.d.ts.map +1 -0
  107. package/dist/queries.js +272 -0
  108. package/dist/query.d.ts +10 -0
  109. package/dist/query.d.ts.map +1 -1
  110. package/dist/questions.d.ts +34 -0
  111. package/dist/questions.d.ts.map +1 -0
  112. package/dist/questions.js +122 -0
  113. package/dist/reach.d.ts +8 -6
  114. package/dist/reach.d.ts.map +1 -1
  115. package/dist/reach.js +156 -99
  116. package/dist/react.d.ts +7 -1
  117. package/dist/react.d.ts.map +1 -1
  118. package/dist/read.d.ts +7 -0
  119. package/dist/read.d.ts.map +1 -0
  120. package/dist/read.js +17 -0
  121. package/dist/reads.d.ts +36 -0
  122. package/dist/reads.d.ts.map +1 -0
  123. package/dist/reads.js +136 -0
  124. package/dist/recovery.d.ts.map +1 -1
  125. package/dist/recovery.js +9 -1
  126. package/dist/refresh.d.ts +1 -1
  127. package/dist/refresh.d.ts.map +1 -1
  128. package/dist/refresh.js +4 -0
  129. package/dist/refusal.d.ts +5 -0
  130. package/dist/refusal.d.ts.map +1 -0
  131. package/dist/refusal.js +56 -0
  132. package/dist/replica.d.ts +0 -3
  133. package/dist/replica.d.ts.map +1 -1
  134. package/dist/replica.js +23 -6
  135. package/dist/room-drafts.d.ts +24 -0
  136. package/dist/room-drafts.d.ts.map +1 -0
  137. package/dist/room-drafts.js +216 -0
  138. package/dist/rows.d.ts.map +1 -1
  139. package/dist/rows.js +2 -2
  140. package/dist/runtime.d.ts +42 -0
  141. package/dist/runtime.d.ts.map +1 -0
  142. package/dist/runtime.js +1 -0
  143. package/dist/save.d.ts.map +1 -1
  144. package/dist/save.js +11 -8
  145. package/dist/saves.d.ts +70 -0
  146. package/dist/saves.d.ts.map +1 -0
  147. package/dist/saves.js +1004 -0
  148. package/dist/seed.d.ts +11 -2
  149. package/dist/seed.d.ts.map +1 -1
  150. package/dist/seed.js +71 -0
  151. package/dist/shown.d.ts +31 -0
  152. package/dist/shown.d.ts.map +1 -0
  153. package/dist/shown.js +294 -0
  154. package/dist/socket/admission.d.ts +7 -0
  155. package/dist/socket/admission.d.ts.map +1 -0
  156. package/dist/socket/admission.js +39 -0
  157. package/dist/socket/answers.d.ts +6 -0
  158. package/dist/socket/answers.d.ts.map +1 -0
  159. package/dist/socket/answers.js +20 -0
  160. package/dist/socket/lanes.d.ts +20 -0
  161. package/dist/socket/lanes.d.ts.map +1 -0
  162. package/dist/socket/lanes.js +77 -0
  163. package/dist/socket/reads.d.ts +10 -0
  164. package/dist/socket/reads.d.ts.map +1 -0
  165. package/dist/socket/reads.js +31 -0
  166. package/dist/socket/rooms.d.ts +43 -0
  167. package/dist/socket/rooms.d.ts.map +1 -0
  168. package/dist/socket/rooms.js +90 -0
  169. package/dist/socket/session.d.ts +39 -0
  170. package/dist/socket/session.d.ts.map +1 -0
  171. package/dist/socket/session.js +23 -0
  172. package/dist/socket.d.ts +70 -0
  173. package/dist/socket.d.ts.map +1 -0
  174. package/dist/socket.js +175 -0
  175. package/dist/source.d.ts +61 -0
  176. package/dist/source.d.ts.map +1 -0
  177. package/dist/source.js +81 -0
  178. package/dist/status.d.ts +0 -2
  179. package/dist/status.d.ts.map +1 -1
  180. package/dist/status.js +0 -6
  181. package/dist/store/fences.d.ts +16 -0
  182. package/dist/store/fences.d.ts.map +1 -0
  183. package/dist/store/fences.js +26 -0
  184. package/dist/store/installs.d.ts +30 -0
  185. package/dist/store/installs.d.ts.map +1 -0
  186. package/dist/store/installs.js +34 -0
  187. package/dist/store/readiness.d.ts +10 -0
  188. package/dist/store/readiness.d.ts.map +1 -0
  189. package/dist/store/readiness.js +52 -0
  190. package/dist/store/records.d.ts +212 -0
  191. package/dist/store/records.d.ts.map +1 -0
  192. package/dist/store/records.js +408 -0
  193. package/dist/store/room-held.d.ts +8 -0
  194. package/dist/store/room-held.d.ts.map +1 -0
  195. package/dist/store/room-held.js +17 -0
  196. package/dist/store/space.d.ts +11 -0
  197. package/dist/store/space.d.ts.map +1 -0
  198. package/dist/store/space.js +43 -0
  199. package/dist/store/watchers.d.ts +17 -0
  200. package/dist/store/watchers.d.ts.map +1 -0
  201. package/dist/store/watchers.js +57 -0
  202. package/dist/store/writes.d.ts +24 -0
  203. package/dist/store/writes.d.ts.map +1 -0
  204. package/dist/store/writes.js +125 -0
  205. package/dist/store.d.ts +107 -142
  206. package/dist/store.d.ts.map +1 -1
  207. package/dist/store.js +143 -361
  208. package/dist/stored.d.ts +55 -28
  209. package/dist/stored.d.ts.map +1 -1
  210. package/dist/stored.js +210 -79
  211. package/dist/surface.d.ts +88 -0
  212. package/dist/surface.d.ts.map +1 -0
  213. package/dist/surface.js +222 -0
  214. package/dist/sync.d.ts +20 -0
  215. package/dist/sync.d.ts.map +1 -0
  216. package/dist/sync.js +531 -0
  217. package/dist/tables.d.ts +108 -20
  218. package/dist/tables.d.ts.map +1 -1
  219. package/dist/tables.js +189 -36
  220. package/dist/tallies.d.ts +18 -0
  221. package/dist/tallies.d.ts.map +1 -0
  222. package/dist/tallies.js +129 -0
  223. package/dist/validation.d.ts.map +1 -1
  224. package/dist/validation.js +4 -4
  225. package/dist/wire.d.ts +8 -1
  226. package/dist/wire.d.ts.map +1 -1
  227. package/dist/wire.js +4 -0
  228. package/package.json +2 -2
  229. package/dist/fence.d.ts +0 -6
  230. package/dist/fence.d.ts.map +0 -1
  231. package/dist/fence.js +0 -10
@@ -1,9 +1,12 @@
1
1
  import { cursorOf, decodedCursor } from '@agentshouse/mdmodel';
2
2
  import { conversationChangeSet } from './change-set.js';
3
+ import { heldFacts } from './conversation-row.js';
3
4
  import { runtimeError } from './failure.js';
4
5
  import { refusedForSpace } from './durability.js';
5
6
  import { changeBatchIdentity, chatDraftRevision } from './identity.js';
6
7
  import { OFFLINE } from './outcome.js';
8
+ import { jobsOf, runningLine } from './parts.js';
9
+ import { openQuestion, questionAsked } from './questions.js';
7
10
  import { untilAnswered } from './recovery.js';
8
11
  import { capped, FROM_START, messageOrder, precedes } from './windows.js';
9
12
  const QUIET = {
@@ -42,11 +45,14 @@ export function unheldView(conversation) {
42
45
  link: null,
43
46
  deliveryUnknown: [],
44
47
  chatDraft: null,
48
+ jobs: [],
45
49
  };
46
50
  }
47
51
  export const SESSION_STARTED = 'Session started';
48
52
  const NEW_CHAT = 'new:';
49
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;
50
56
  export function newChat(agent) {
51
57
  return `${NEW_CHAT}${agent}`;
52
58
  }
@@ -254,10 +260,6 @@ function returned(draft, submission, others) {
254
260
  }, others);
255
261
  }
256
262
  export function createConversationStore(deps) {
257
- const stored = new Set();
258
- const reads = new Map();
259
- const partials = new Set();
260
- const drafts = new Map();
261
263
  const sending = new Map();
262
264
  const behind = new Set();
263
265
  const catching = new Map();
@@ -265,15 +267,16 @@ export function createConversationStore(deps) {
265
267
  const lives = new Map();
266
268
  const unsent = new Map();
267
269
  const views = new Map();
268
- const listing = new Map();
270
+ const heardNewest = new Map();
269
271
  let relisting = null;
270
- let listCursor = null;
271
272
  const creations = new Set();
272
273
  const listWatchers = new Set();
273
274
  const unlisted = new Set();
274
275
  let listKnown = false;
275
- let listHeld = false;
276
- let listKept = false;
276
+ let listOwed = false;
277
+ let listTelling = false;
278
+ let rowTimer = null;
279
+ const closing = new AbortController();
277
280
  const agentWatchers = new Set();
278
281
  const unnamed = new Set();
279
282
  let agentsShown = null;
@@ -284,8 +287,7 @@ export function createConversationStore(deps) {
284
287
  function liveOf(conversation) {
285
288
  return lives.get(conversation) ?? QUIET;
286
289
  }
287
- function viewOf(conversation, held, frame) {
288
- const draft = drafts.get(conversation);
290
+ function viewOf(conversation, held, frame, draft, ended, marked) {
289
291
  const facts = liveOf(conversation);
290
292
  return {
291
293
  conversation,
@@ -299,16 +301,17 @@ export function createConversationStore(deps) {
299
301
  turn: structuredClone(facts.turn),
300
302
  questions: structuredClone(facts.questions),
301
303
  process: facts.process === null ? null : { ...facts.process },
302
- processEnded: listing.get(conversation)?.processEnded ?? null,
304
+ processEnded: ended,
303
305
  commands: structuredClone(facts.commands),
304
306
  options: structuredClone(facts.options),
305
307
  starting: facts.starting,
306
308
  subagents: structuredClone(facts.subagents),
307
309
  link: facts.link === null ? null : { ...facts.link },
308
310
  deliveryUnknown: [...facts.deliveryUnknown],
309
- chatDraft: draft === undefined || unwritten(draft)
310
- ? null
311
- : { 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),
312
315
  };
313
316
  }
314
317
  function emit(conversation) {
@@ -320,7 +323,7 @@ export function createConversationStore(deps) {
320
323
  const ready = [...view.watchers].filter((watcher) => watcher.ready);
321
324
  if (ready.length === 0)
322
325
  continue;
323
- 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);
324
327
  for (const watcher of ready)
325
328
  watcher.notify(value);
326
329
  }
@@ -330,11 +333,13 @@ export function createConversationStore(deps) {
330
333
  }
331
334
  async function slide(local, conversation, arriving) {
332
335
  let count;
336
+ let marked;
333
337
  for (const view of views.values()) {
334
338
  if (view.conversation !== conversation || view.window === null)
335
339
  continue;
336
340
  if (arriving === null) {
337
341
  view.window = [];
342
+ view.marked = [];
338
343
  view.more = false;
339
344
  if (view.total !== null)
340
345
  view.total = 0;
@@ -344,6 +349,10 @@ export function createConversationStore(deps) {
344
349
  const next = windowOf(view.window, joining, view.asked.limit);
345
350
  view.window = next.messages;
346
351
  view.more ||= next.dropped;
352
+ if (arriving.length > 0) {
353
+ marked ??= await local.jobMarkers(conversation);
354
+ view.marked = marked;
355
+ }
347
356
  if (view.total !== null && arriving.length > 0) {
348
357
  count ??= await local.messageCount(conversation);
349
358
  view.total = count;
@@ -356,82 +365,179 @@ export function createConversationStore(deps) {
356
365
  return 'asking';
357
366
  return facts.turn === null ? 'idle' : 'working';
358
367
  }
359
- function entries() {
360
- 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
+ asked: questionAsked(liveOf(conversation).questions),
376
+ };
377
+ }
378
+ async function entries() {
379
+ const list = await heldList();
380
+ if (list === null)
381
+ return null;
382
+ const held = list.length === 0
383
+ ? []
384
+ : await deps.durably((local) => local.conversationRows(list.map(({ conversation }) => conversation)));
385
+ const rows = new Map(held.map((one) => [one.conversation, one]));
386
+ return list.map((entry) => ({
361
387
  conversation: entry.conversation,
362
- agent: { identity: entry.agent, name: entry.agentName },
388
+ agent: { identity: entry.agent, name: entry.agentName, retired: entry.agentRetired },
363
389
  name: entry.name,
364
390
  created: entry.created,
365
391
  finished: entry.finished,
366
392
  outcome: entry.outcome,
367
393
  waiting: entry.waiting,
368
- state: stateOf(entry.conversation),
369
- started: lives.get(entry.conversation)?.turn?.started ?? null,
394
+ ...rowOf(entry.conversation),
395
+ ...heldFacts(rows.get(entry.conversation), entry),
370
396
  }));
371
397
  }
372
- function keepList() {
373
- if (!listKnown || listKept)
374
- return;
375
- listKept = true;
376
- queueMicrotask(() => {
377
- listKept = false;
378
- if (deps.closed())
398
+ async function heldList() {
399
+ const stored = (await (await deps.local()).facts()).conversationList;
400
+ if (stored !== null)
401
+ return stored;
402
+ return !listKnown && deps.stream() !== undefined && deps.online() ? null : [];
403
+ }
404
+ async function endedIn(conversation) {
405
+ const list = await heldList();
406
+ return list?.find((entry) => entry.conversation === conversation)?.processEnded ?? null;
407
+ }
408
+ function listChanged(change) {
409
+ void deps
410
+ .durably(async (local) => {
411
+ const { conversationList, conversationCursor } = await local.facts();
412
+ const kept = change.replacing ? [] : (conversationList ?? []);
413
+ const held = new Map(kept.map((entry) => [entry.conversation, entry]));
414
+ for (const entry of change.set) {
415
+ held.set(entry.conversation, { ...entry });
416
+ creations.delete(entry.conversation);
417
+ }
418
+ if (change.gone !== null)
419
+ held.delete(change.gone);
420
+ const list = [...held.values()];
421
+ if (JSON.stringify(list) !== JSON.stringify(conversationList)) {
422
+ await local.fact('conversationList', list);
423
+ }
424
+ if (change.cursor !== undefined) {
425
+ if (change.cursor !== conversationCursor) {
426
+ await local.fact('conversationCursor', change.cursor);
427
+ }
428
+ deps.stream()?.hold(change.cursor);
429
+ }
430
+ return list;
431
+ })
432
+ .catch(async () => (await (await deps.local()).facts()).conversationList ?? [])
433
+ .then((list) => {
434
+ if (deps.closed() || change.paged === true)
379
435
  return;
380
- const entries = [...listing.values()];
381
- const cursor = listCursor;
382
- void deps
383
- .durably(async (local) => {
384
- await local.fact('conversationList', entries);
385
- await local.fact('conversationCursor', cursor);
386
- })
387
- .catch(() => { });
436
+ endedAnew(list);
437
+ if (change.cursor !== undefined) {
438
+ listKnown = true;
439
+ settleBehind(list);
440
+ openingEnded();
441
+ deps.settled();
442
+ }
443
+ emitList();
444
+ if (!listKnown)
445
+ return;
446
+ retiredDrafts(list);
447
+ if (change.cursor === undefined &&
448
+ (list.length !== replicated.size ||
449
+ list.some(({ conversation }) => !replicated.has(conversation)))) {
450
+ deps.again();
451
+ }
452
+ }, () => { });
453
+ }
454
+ function endedAnew(list) {
455
+ const entries = new Map(list.map((entry) => [entry.conversation, entry]));
456
+ for (const view of views.values()) {
457
+ const ended = entries.get(view.conversation)?.processEnded ?? null;
458
+ if (view.window === null || ended === view.ended)
459
+ continue;
460
+ view.ended = ended;
461
+ emit(view.conversation);
462
+ }
463
+ }
464
+ let listShown = Promise.resolve();
465
+ function shownList(work) {
466
+ const reading = heldList();
467
+ listShown = listShown.then(async () => {
468
+ const list = await reading.catch(() => null);
469
+ if (list === null || deps.closed())
470
+ return;
471
+ work(list);
388
472
  });
389
473
  }
390
- function listed(entry) {
391
- const ended = listing.get(entry.conversation)?.processEnded ?? null;
392
- listing.set(entry.conversation, { ...entry });
393
- creations.delete(entry.conversation);
394
- if (entry.processEnded !== ended)
395
- emit(entry.conversation);
474
+ async function told() {
475
+ try {
476
+ while (listOwed && !deps.closed()) {
477
+ listOwed = false;
478
+ const value = await untilAnswered(entries, closing.signal);
479
+ if (deps.closed())
480
+ return;
481
+ if (value === null)
482
+ continue;
483
+ for (const watcher of listWatchers)
484
+ watcher(value);
485
+ for (const watcher of [...unlisted]) {
486
+ unlisted.delete(watcher);
487
+ listWatchers.add(watcher);
488
+ watcher(value);
489
+ }
490
+ }
491
+ }
492
+ finally {
493
+ listTelling = false;
494
+ }
495
+ }
496
+ function tellList() {
497
+ if (deps.closed() || (listWatchers.size === 0 && unlisted.size === 0))
498
+ return;
499
+ listOwed = true;
500
+ if (listTelling)
501
+ return;
502
+ listTelling = true;
503
+ void told().catch(() => { });
504
+ }
505
+ function tellSoon() {
506
+ if (rowTimer !== null)
507
+ return;
508
+ rowTimer = setTimeout(() => {
509
+ rowTimer = null;
510
+ tellList();
511
+ }, ROW_INTERVAL_MS);
396
512
  }
397
513
  function emitList() {
398
514
  if (deps.closed())
399
515
  return;
400
- if (listWatchers.size > 0) {
401
- const value = entries();
402
- for (const watcher of listWatchers)
403
- watcher(value);
404
- }
516
+ tellList();
405
517
  emitAgents();
406
518
  learned();
407
519
  }
408
520
  function learned() {
409
521
  if (deps.closed())
410
522
  return;
411
- if (!listKnown && !listHeld && deps.stream() !== undefined && deps.online())
523
+ if (unlisted.size > 0)
524
+ tellList();
525
+ if (unnamed.size === 0)
412
526
  return;
413
- if (unlisted.size > 0) {
414
- const value = entries();
415
- for (const watcher of [...unlisted]) {
416
- unlisted.delete(watcher);
417
- listWatchers.add(watcher);
418
- watcher(value);
419
- }
420
- }
421
- if (unnamed.size > 0) {
422
- const value = agents();
527
+ shownList((list) => {
528
+ const value = agents(list);
423
529
  for (const watcher of [...unnamed]) {
424
530
  unnamed.delete(watcher);
425
531
  agentWatchers.add(watcher);
426
532
  watcher(value);
427
533
  }
428
- }
534
+ });
429
535
  }
430
- function agents() {
536
+ function agents(list) {
431
537
  const named = new Map();
432
538
  for (const agent of listedAgents ?? [])
433
539
  named.set(agent.agent, agent);
434
- for (const entry of listing.values()) {
540
+ for (const entry of list) {
435
541
  named.set(entry.agent, { name: entry.agentName, retired: entry.agentRetired });
436
542
  }
437
543
  return [...named]
@@ -461,31 +567,35 @@ export function createConversationStore(deps) {
461
567
  function emitAgents() {
462
568
  if (deps.closed() || agentWatchers.size === 0)
463
569
  return;
464
- const value = agents();
465
- const shown = JSON.stringify(value);
466
- if (shown === agentsShown)
467
- return;
468
- agentsShown = shown;
469
- for (const watcher of agentWatchers)
470
- watcher(value);
570
+ shownList((list) => {
571
+ const value = agents(list);
572
+ const shown = JSON.stringify(value);
573
+ if (shown === agentsShown)
574
+ return;
575
+ agentsShown = shown;
576
+ for (const watcher of agentWatchers)
577
+ watcher(value);
578
+ });
471
579
  }
472
580
  function setLive(conversation, next) {
473
- const before = stateOf(conversation);
474
- const began = lives.get(conversation)?.turn?.started;
581
+ const before = rowOf(conversation);
475
582
  if (next === null)
476
583
  lives.delete(conversation);
477
584
  else
478
585
  lives.set(conversation, next);
479
- if (!listing.has(conversation))
480
- return;
481
- if (stateOf(conversation) !== before || next?.turn?.started !== began)
586
+ const after = rowOf(conversation);
587
+ if (after.state !== before.state || after.started !== before.started)
482
588
  emitList();
589
+ else if (after.line !== before.line ||
590
+ after.question !== before.question ||
591
+ after.asked !== before.asked)
592
+ tellSoon();
483
593
  }
484
- function retiredChat(conversation) {
594
+ function retiredChat(conversation, list) {
485
595
  if (!conversation.startsWith(NEW_CHAT))
486
596
  return false;
487
597
  const agent = conversation.slice(NEW_CHAT.length);
488
- return [...listing.values()].some((entry) => entry.agent === agent && entry.agentRetired);
598
+ return list.some((entry) => entry.agent === agent && entry.agentRetired);
489
599
  }
490
600
  function live(conversation, change) {
491
601
  const owed = unsent.get(conversation);
@@ -543,17 +653,23 @@ export function createConversationStore(deps) {
543
653
  }
544
654
  async function remove(leaving) {
545
655
  await deps.durably(async (local) => {
656
+ const kept = new Set([
657
+ ...(await local.conversations(leaving)).map(({ conversation }) => conversation),
658
+ ...(await local.chatDrafts())
659
+ .filter(({ conversation }) => leaving.includes(conversation))
660
+ .map(({ conversation }) => conversation),
661
+ ]);
546
662
  await local.dropConversations(leaving);
547
663
  const changed = [];
548
664
  for (const conversation of leaving) {
549
- if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
665
+ if (kept.has(conversation) || lives.has(conversation))
550
666
  changed.push(conversation);
667
+ for (const view of views.values()) {
668
+ if (view.conversation === conversation)
669
+ view.draft = null;
551
670
  }
552
- stored.delete(conversation);
553
- reads.delete(conversation);
554
- partials.delete(conversation);
555
- drafts.delete(conversation);
556
671
  behind.delete(conversation);
672
+ heardNewest.delete(conversation);
557
673
  unsent.delete(conversation);
558
674
  setLive(conversation, null);
559
675
  await slide(local, conversation, null);
@@ -562,19 +678,68 @@ export function createConversationStore(deps) {
562
678
  deps.publish(conversationChangeSet(changed));
563
679
  });
564
680
  }
565
- function missed(conversation) {
566
- const read = reads.get(conversation);
567
- const newest = listing.get(conversation)?.newest;
681
+ function newestOfListed(entry) {
682
+ if (entry === undefined)
683
+ return undefined;
684
+ const heard = heardNewest.get(entry.conversation);
685
+ if (heard === undefined)
686
+ return entry.newest;
687
+ return entry.newest === null || heard > entry.newest ? heard : entry.newest;
688
+ }
689
+ function missed(conversation, held, entry) {
690
+ const read = held?.read;
691
+ const newest = newestOfListed(entry);
568
692
  return (read === undefined ||
569
693
  newest === undefined ||
570
- partials.has(conversation) ||
694
+ held?.partial === true ||
571
695
  (newest !== null && (read === null || newest > read)));
572
696
  }
573
- function noted(conversation, message) {
574
- const entry = listing.get(conversation);
575
- if (entry === undefined || (entry.newest !== null && entry.newest >= message.position))
697
+ async function readsOf(local, conversations) {
698
+ return new Map((await local.conversations(conversations)).map((held) => [held.conversation, held]));
699
+ }
700
+ function retiredDrafts(list) {
701
+ if (!list.some((entry) => entry.agentRetired))
576
702
  return;
577
- listing.set(conversation, { ...entry, newest: message.position });
703
+ void deps
704
+ .local()
705
+ .then((local) => local.chatDrafts())
706
+ .then((held) => {
707
+ if (!deps.closed() && held.some(({ conversation }) => retiredChat(conversation, list))) {
708
+ deps.again();
709
+ }
710
+ }, () => { });
711
+ }
712
+ function settleBehind(list) {
713
+ void deps
714
+ .durably(async (local) => {
715
+ const entries = new Map(list.map((entry) => [entry.conversation, entry]));
716
+ const named = [...replicated].filter((conversation) => entries.has(conversation));
717
+ const held = await readsOf(local, named);
718
+ let owed = false;
719
+ for (const conversation of named) {
720
+ if (!missed(conversation, held.get(conversation), entries.get(conversation))) {
721
+ behind.delete(conversation);
722
+ }
723
+ else if (!behind.has(conversation)) {
724
+ gap(conversation);
725
+ owed = true;
726
+ }
727
+ }
728
+ return owed;
729
+ })
730
+ .then((owed) => {
731
+ if (deps.closed())
732
+ return;
733
+ deps.settled();
734
+ if (owed)
735
+ deps.again();
736
+ }, () => { });
737
+ }
738
+ function noted(conversation, message) {
739
+ const heard = heardNewest.get(conversation);
740
+ if (heard === undefined || message.position > heard) {
741
+ heardNewest.set(conversation, message.position);
742
+ }
578
743
  }
579
744
  function gap(conversation) {
580
745
  gaps.set(conversation, (gaps.get(conversation) ?? 0) + 1);
@@ -584,7 +749,7 @@ export function createConversationStore(deps) {
584
749
  return deps.durably(async (local) => {
585
750
  if (!replicated.has(conversation))
586
751
  return;
587
- const read = reads.get(conversation);
752
+ const read = (await readsOf(local, [conversation])).get(conversation)?.read;
588
753
  const advances = read !== undefined &&
589
754
  !behind.has(conversation) &&
590
755
  (read === null || message.position > read);
@@ -594,19 +759,18 @@ export function createConversationStore(deps) {
594
759
  messages: [message],
595
760
  ...(advances ? { read: { at: message.position, partial: false } } : {}),
596
761
  });
597
- stored.add(conversation);
598
- if (advances)
599
- reads.set(conversation, message.position);
600
762
  sealTurns(conversation, new Set([message.turn]));
601
763
  await slide(local, conversation, [message]);
602
764
  deps.publish(conversationChangeSet([conversation]));
765
+ tellList();
603
766
  });
604
767
  }
605
768
  async function readHistories(conversations, ended) {
606
769
  const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
770
+ const held = await readsOf(await deps.local(), conversations);
607
771
  let waiting = conversations.map((conversation) => ({
608
772
  conversation,
609
- after: reads.get(conversation) ?? undefined,
773
+ after: held.get(conversation)?.read ?? undefined,
610
774
  }));
611
775
  while (waiting.length > 0) {
612
776
  const asking = waiting.slice(0, HISTORY_CONVERSATIONS);
@@ -662,15 +826,10 @@ export function createConversationStore(deps) {
662
826
  return;
663
827
  const whole = next === undefined;
664
828
  const cursor = next ?? newestOf(arriving, from);
665
- const partial = !whole && (partials.has(conversation) || !reads.has(conversation));
829
+ const held = (await readsOf(local, [conversation])).get(conversation);
830
+ const partial = !whole && (held?.partial === true || held?.read === undefined);
666
831
  const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
667
832
  await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
668
- stored.add(conversation);
669
- reads.set(conversation, cursor);
670
- if (partial)
671
- partials.add(conversation);
672
- else
673
- partials.delete(conversation);
674
833
  sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
675
834
  if (whole && (gaps.get(conversation) ?? 0) === generation)
676
835
  behind.delete(conversation);
@@ -678,6 +837,8 @@ export function createConversationStore(deps) {
678
837
  if (arriving.length > 0 || sealing || whole) {
679
838
  deps.publish(conversationChangeSet([conversation]));
680
839
  }
840
+ if (arriving.length > 0)
841
+ tellSoon();
681
842
  });
682
843
  }
683
844
  function heard(event) {
@@ -744,17 +905,24 @@ export function createConversationStore(deps) {
744
905
  emit(conversation);
745
906
  }
746
907
  async function kept(local, conversation, following) {
747
- if (following === null || empty(following)) {
908
+ const shown = following === null || empty(following) ? null : following;
909
+ if (shown === null)
748
910
  await local.dropChatDraft(conversation);
749
- drafts.delete(conversation);
750
- }
751
- else {
752
- await local.keepChatDrafts([following]);
753
- drafts.set(conversation, following);
911
+ else
912
+ await local.keepChatDrafts([shown]);
913
+ shownDraft(conversation, shown);
914
+ }
915
+ function shownDraft(conversation, draft) {
916
+ for (const view of views.values()) {
917
+ if (view.conversation === conversation)
918
+ view.draft = draft;
754
919
  }
755
920
  }
921
+ async function draftAt(local, conversation) {
922
+ return (await local.chatDraft(conversation)) ?? undefined;
923
+ }
756
924
  async function sealed(local, conversation, turns) {
757
- const held = drafts.get(conversation);
925
+ const held = await draftAt(local, conversation);
758
926
  const submitted = held?.submitted ?? [];
759
927
  const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
760
928
  if (held === undefined || left.length === submitted.length)
@@ -765,7 +933,7 @@ export function createConversationStore(deps) {
765
933
  function moved(agent, operation, to) {
766
934
  const from = newChat(agent);
767
935
  return deps.durably(async (local) => {
768
- const held = drafts.get(from);
936
+ const held = await draftAt(local, from);
769
937
  const earlier = held?.moves ?? [];
770
938
  if (earlier.includes(operation))
771
939
  return;
@@ -775,18 +943,20 @@ export function createConversationStore(deps) {
775
943
  revision: chatDraftRevision(),
776
944
  };
777
945
  const { moves: _answered, ...unsent } = held ?? blank;
778
- const taken = drafts.has(to) || empty(unsent) ? [] : [{ ...unsent, conversation: to }];
946
+ const taken = (await draftAt(local, to)) !== undefined || empty(unsent)
947
+ ? []
948
+ : [{ ...unsent, conversation: to }];
779
949
  const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
780
950
  await local.keepChatDrafts([left, ...taken]);
781
- drafts.set(from, left);
951
+ shownDraft(from, left);
782
952
  for (const one of taken)
783
- drafts.set(to, one);
953
+ shownDraft(to, one);
784
954
  deps.publish(conversationChangeSet([from, to]));
785
955
  });
786
956
  }
787
957
  function withDraft(conversation, next) {
788
958
  return deps.durably(async (local) => {
789
- const following = await next(drafts.get(conversation), local);
959
+ const following = await next(await draftAt(local, conversation), local);
790
960
  if (following === undefined)
791
961
  return;
792
962
  await kept(local, conversation, following);
@@ -868,7 +1038,8 @@ export function createConversationStore(deps) {
868
1038
  return deps.durably(async (local) => {
869
1039
  const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before, phrase);
870
1040
  const total = asked.total ? await local.messageCount(conversation, phrase) : null;
871
- return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total)));
1041
+ const marked = await local.jobMarkers(conversation);
1042
+ return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total), (await local.chatDraft(conversation)) ?? null, await endedIn(conversation), marked));
872
1043
  });
873
1044
  },
874
1045
  async create(input) {
@@ -876,8 +1047,10 @@ export function createConversationStore(deps) {
876
1047
  const conversation = answered(result, 'conversation_id');
877
1048
  if (conversation === null)
878
1049
  return result;
879
- if (!listing.has(conversation))
1050
+ const listed = (await heldList()) ?? [];
1051
+ if (!listed.some((entry) => entry.conversation === conversation)) {
880
1052
  creations.add(conversation);
1053
+ }
881
1054
  const creation = creationOf(input);
882
1055
  if (creation !== null)
883
1056
  await moved(creation.agent, creation.operation, conversation);
@@ -945,43 +1118,40 @@ export function createConversationStore(deps) {
945
1118
  return {
946
1119
  surface,
947
1120
  chatDraft,
948
- hydrate(held, kept, list, cursor) {
949
- if (list !== null && !listKnown) {
950
- listing.clear();
951
- for (const entry of list)
952
- listing.set(entry.conversation, { ...entry });
953
- listHeld = true;
954
- listCursor = cursor;
955
- deps.stream()?.hold(cursor);
956
- }
957
- stored.clear();
958
- reads.clear();
959
- partials.clear();
960
- for (const entry of held) {
961
- stored.add(entry.conversation);
962
- if (entry.read !== undefined)
963
- reads.set(entry.conversation, entry.read);
964
- if (entry.partial)
965
- partials.add(entry.conversation);
966
- }
967
- drafts.clear();
968
- for (const record of kept)
969
- drafts.set(record.conversation, record);
970
- if (listHeld)
971
- learned();
1121
+ async opened(local) {
1122
+ const { conversationList, conversationCursor } = await local.facts();
1123
+ if (conversationList === null || listKnown)
1124
+ return;
1125
+ deps.stream()?.hold(conversationCursor);
1126
+ learned();
972
1127
  },
973
1128
  async reconcile() {
974
1129
  if (!listKnown)
975
1130
  return;
976
- const named = new Set(listing.keys());
1131
+ const local = await deps.local();
1132
+ const [held, kept, { conversationList }] = await Promise.all([
1133
+ local.conversations(),
1134
+ local.chatDrafts(),
1135
+ local.facts(),
1136
+ ]);
1137
+ const list = conversationList ?? [];
1138
+ const entries = new Map(list.map((entry) => [entry.conversation, entry]));
1139
+ const named = new Set(entries.keys());
1140
+ const reads = new Map(held.map((one) => [one.conversation, one]));
977
1141
  const leaving = [
978
- ...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
1142
+ ...new Set([
1143
+ ...replicated,
1144
+ ...reads.keys(),
1145
+ ...kept.map(({ conversation }) => conversation),
1146
+ ]),
979
1147
  ].filter((conversation) => !named.has(conversation) &&
980
1148
  !creations.has(conversation) &&
981
- (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
1149
+ (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation, list)));
982
1150
  for (const conversation of named) {
983
- if (!replicated.has(conversation) && missed(conversation))
1151
+ if (!replicated.has(conversation) &&
1152
+ missed(conversation, reads.get(conversation), entries.get(conversation))) {
984
1153
  behind.add(conversation);
1154
+ }
985
1155
  }
986
1156
  replicated = named;
987
1157
  handReplicated();
@@ -1012,12 +1182,16 @@ export function createConversationStore(deps) {
1012
1182
  progress.complete = false;
1013
1183
  },
1014
1184
  async resend(progress) {
1015
- for (const [conversation, held] of [...drafts]) {
1016
- for (const submission of held.submitted ?? []) {
1017
- if (submission.turn !== null || sending.has(submission.operation))
1018
- continue;
1185
+ const local = await deps.local();
1186
+ const tried = new Set();
1187
+ for (const { conversation } of await local.chatDrafts()) {
1188
+ for (;;) {
1189
+ const submission = (await local.chatDraft(conversation))?.submitted?.find((one) => one.turn === null && !sending.has(one.operation) && !tried.has(one.operation));
1190
+ if (submission === undefined)
1191
+ break;
1019
1192
  if (deps.closed())
1020
1193
  return;
1194
+ tried.add(submission.operation);
1021
1195
  const result = await dispatched(conversation, submission);
1022
1196
  if (result.kind === 'revoked-authorization')
1023
1197
  return;
@@ -1055,6 +1229,9 @@ export function createConversationStore(deps) {
1055
1229
  asked,
1056
1230
  watchers: new Set(),
1057
1231
  window: null,
1232
+ draft: null,
1233
+ ended: null,
1234
+ marked: [],
1058
1235
  more: false,
1059
1236
  total: null,
1060
1237
  };
@@ -1066,7 +1243,10 @@ export function createConversationStore(deps) {
1066
1243
  if (view.window !== null)
1067
1244
  return;
1068
1245
  const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
1246
+ view.draft = (await local.chatDraft(conversation)) ?? null;
1247
+ view.ended = await endedIn(conversation);
1069
1248
  view.window = messages;
1249
+ view.marked = await local.jobMarkers(conversation);
1070
1250
  view.more = more;
1071
1251
  view.total = asked.total ? await local.messageCount(conversation) : null;
1072
1252
  }), reading.signal)
@@ -1075,7 +1255,7 @@ export function createConversationStore(deps) {
1075
1255
  return;
1076
1256
  watcher.ready = true;
1077
1257
  const window = view.window ?? [];
1078
- watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
1258
+ watcher.notify(viewOf(conversation, window, sharedFrame(view, window), view.draft, view.ended, view.marked));
1079
1259
  })
1080
1260
  .catch(() => { });
1081
1261
  return () => {
@@ -1110,58 +1290,43 @@ export function createConversationStore(deps) {
1110
1290
  },
1111
1291
  receive(event) {
1112
1292
  if (deps.closed())
1113
- return false;
1114
- if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
1115
- if (event.kind === 'list') {
1116
- if (event.whole)
1117
- relisting ??= new Map();
1118
- for (const entry of event.entries) {
1119
- if (relisting === null)
1120
- listed(entry);
1121
- else
1122
- relisting.set(entry.conversation, { ...entry });
1123
- }
1124
- if (event.more)
1125
- return false;
1126
- if (relisting !== null) {
1127
- listing.clear();
1128
- for (const entry of relisting.values())
1129
- listed(entry);
1130
- relisting = null;
1131
- }
1132
- listCursor = event.cursor;
1133
- deps.stream()?.hold(event.cursor);
1134
- listKnown = true;
1135
- for (const conversation of replicated) {
1136
- if (!listing.has(conversation))
1137
- continue;
1138
- if (!missed(conversation))
1139
- behind.delete(conversation);
1140
- else if (!behind.has(conversation))
1141
- gap(conversation);
1142
- }
1143
- openingEnded();
1144
- }
1145
- else if (event.kind === 'listed') {
1146
- listed(event.entry);
1147
- relisting?.set(event.entry.conversation, { ...event.entry });
1148
- }
1149
- else {
1150
- listing.delete(event.conversation);
1151
- relisting?.delete(event.conversation);
1293
+ return;
1294
+ if (event.kind === 'list') {
1295
+ if (event.whole)
1296
+ relisting ??= new Map();
1297
+ if (relisting !== null) {
1298
+ for (const entry of event.entries)
1299
+ relisting.set(entry.conversation, { ...entry });
1152
1300
  }
1153
- keepList();
1154
- emitList();
1155
- return ((event.kind !== 'list' &&
1156
- listKnown &&
1157
- (listing.size !== replicated.size ||
1158
- [...listing.keys()].some((conversation) => !replicated.has(conversation)))) ||
1159
- (listKnown && [...drafts.keys()].some(retiredChat)));
1301
+ if (event.more && relisting !== null)
1302
+ return;
1303
+ const whole = relisting;
1304
+ relisting = null;
1305
+ listChanged({
1306
+ replacing: whole !== null,
1307
+ set: whole === null ? event.entries : [...whole.values()],
1308
+ gone: null,
1309
+ ...(event.more ? { paged: true } : { cursor: event.cursor }),
1310
+ });
1311
+ return;
1312
+ }
1313
+ if (event.kind === 'listed') {
1314
+ relisting?.set(event.entry.conversation, { ...event.entry });
1315
+ listChanged({ replacing: false, set: [event.entry], gone: null });
1316
+ return;
1317
+ }
1318
+ if (event.kind === 'unlisted') {
1319
+ relisting?.delete(event.conversation);
1320
+ listChanged({ replacing: false, set: [], gone: event.conversation });
1321
+ return;
1160
1322
  }
1161
1323
  heard(event);
1162
- return false;
1163
1324
  },
1164
1325
  close() {
1326
+ closing.abort();
1327
+ if (rowTimer !== null)
1328
+ clearTimeout(rowTimer);
1329
+ rowTimer = null;
1165
1330
  views.clear();
1166
1331
  lives.clear();
1167
1332
  unsent.clear();