@agentshouse/mdruntime 0.1.0-ce5c5bb29be5a → 0.1.0-ce6bc9239a3ee

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 (201) hide show
  1. package/README.md +2 -2
  2. package/dist/acceptance.d.ts +26 -0
  3. package/dist/acceptance.d.ts.map +1 -0
  4. package/dist/acceptance.js +384 -0
  5. package/dist/adapter.d.ts +81 -49
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/attached-model.d.ts +3 -10
  8. package/dist/attached-model.d.ts.map +1 -1
  9. package/dist/attached-model.js +21 -56
  10. package/dist/bootstrap.d.ts +9 -5
  11. package/dist/bootstrap.d.ts.map +1 -1
  12. package/dist/bootstrap.js +209 -75
  13. package/dist/chain.d.ts +2 -1
  14. package/dist/chain.d.ts.map +1 -1
  15. package/dist/change.d.ts +1 -0
  16. package/dist/change.d.ts.map +1 -1
  17. package/dist/chat-types.d.ts +3 -0
  18. package/dist/chat-types.d.ts.map +1 -0
  19. package/dist/chat-types.js +2 -0
  20. package/dist/client.d.ts +16 -6
  21. package/dist/client.d.ts.map +1 -1
  22. package/dist/client.js +3 -1
  23. package/dist/collection.d.ts +2 -1
  24. package/dist/collection.d.ts.map +1 -1
  25. package/dist/compile.d.ts +6 -6
  26. package/dist/compile.d.ts.map +1 -1
  27. package/dist/compile.js +262 -318
  28. package/dist/connection.d.ts +20 -44
  29. package/dist/connection.d.ts.map +1 -1
  30. package/dist/connection.js +1438 -3393
  31. package/dist/context.d.ts +2 -0
  32. package/dist/context.d.ts.map +1 -0
  33. package/dist/context.js +7 -0
  34. package/dist/conversation-row.d.ts +33 -0
  35. package/dist/conversation-row.d.ts.map +1 -0
  36. package/dist/conversation-row.js +47 -0
  37. package/dist/conversation.d.ts +26 -17
  38. package/dist/conversation.d.ts.map +1 -1
  39. package/dist/conversation.js +441 -307
  40. package/dist/durability.d.ts +6 -0
  41. package/dist/durability.d.ts.map +1 -1
  42. package/dist/entry.d.ts +2 -2
  43. package/dist/entry.d.ts.map +1 -1
  44. package/dist/entry.js +17 -7
  45. package/dist/figures.d.ts +48 -0
  46. package/dist/figures.d.ts.map +1 -0
  47. package/dist/figures.js +197 -0
  48. package/dist/host.d.ts +4 -6
  49. package/dist/host.d.ts.map +1 -1
  50. package/dist/host.js +22 -26
  51. package/dist/identity.d.ts +0 -1
  52. package/dist/identity.d.ts.map +1 -1
  53. package/dist/identity.js +0 -3
  54. package/dist/index.d.ts +18 -8
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +5 -1
  57. package/dist/install.d.ts +86 -0
  58. package/dist/install.d.ts.map +1 -0
  59. package/dist/install.js +537 -0
  60. package/dist/job.d.ts +33 -0
  61. package/dist/job.d.ts.map +1 -0
  62. package/dist/job.js +54 -0
  63. package/dist/live.d.ts +5 -4
  64. package/dist/live.d.ts.map +1 -1
  65. package/dist/live.js +63 -22
  66. package/dist/log.d.ts +1 -0
  67. package/dist/log.d.ts.map +1 -1
  68. package/dist/memory.d.ts +11 -4
  69. package/dist/memory.d.ts.map +1 -1
  70. package/dist/memory.js +171 -81
  71. package/dist/messages.d.ts +27 -3
  72. package/dist/messages.d.ts.map +1 -1
  73. package/dist/messages.js +97 -19
  74. package/dist/model.d.ts +2 -9
  75. package/dist/model.d.ts.map +1 -1
  76. package/dist/model.js +0 -9
  77. package/dist/observation.d.ts +2 -2
  78. package/dist/observation.d.ts.map +1 -1
  79. package/dist/observation.js +7 -2
  80. package/dist/outcome.d.ts +1 -0
  81. package/dist/outcome.d.ts.map +1 -1
  82. package/dist/outcome.js +3 -0
  83. package/dist/overlay.d.ts +1 -0
  84. package/dist/overlay.d.ts.map +1 -1
  85. package/dist/overlay.js +11 -0
  86. package/dist/parts.d.ts +59 -0
  87. package/dist/parts.d.ts.map +1 -0
  88. package/dist/parts.js +118 -0
  89. package/dist/query.d.ts +14 -209
  90. package/dist/query.d.ts.map +1 -1
  91. package/dist/query.js +3 -505
  92. package/dist/questions.d.ts +32 -0
  93. package/dist/questions.d.ts.map +1 -0
  94. package/dist/questions.js +118 -0
  95. package/dist/reach.d.ts +12 -12
  96. package/dist/reach.d.ts.map +1 -1
  97. package/dist/reach.js +182 -136
  98. package/dist/react.d.ts +5 -2
  99. package/dist/react.d.ts.map +1 -1
  100. package/dist/read.d.ts +7 -0
  101. package/dist/read.d.ts.map +1 -0
  102. package/dist/read.js +17 -0
  103. package/dist/readiness.d.ts +0 -2
  104. package/dist/readiness.d.ts.map +1 -1
  105. package/dist/reads.d.ts +36 -0
  106. package/dist/reads.d.ts.map +1 -0
  107. package/dist/reads.js +136 -0
  108. package/dist/recovery.d.ts.map +1 -1
  109. package/dist/recovery.js +9 -1
  110. package/dist/refresh.d.ts +6 -4
  111. package/dist/refresh.d.ts.map +1 -1
  112. package/dist/refresh.js +32 -4
  113. package/dist/replica.d.ts +8 -8
  114. package/dist/replica.d.ts.map +1 -1
  115. package/dist/replica.js +50 -15
  116. package/dist/room-draft.d.ts +4 -3
  117. package/dist/room-draft.d.ts.map +1 -1
  118. package/dist/room-draft.js +65 -52
  119. package/dist/room-drafts.d.ts +29 -0
  120. package/dist/room-drafts.d.ts.map +1 -0
  121. package/dist/room-drafts.js +216 -0
  122. package/dist/rows.d.ts +14 -21
  123. package/dist/rows.d.ts.map +1 -1
  124. package/dist/rows.js +48 -184
  125. package/dist/save.d.ts.map +1 -1
  126. package/dist/save.js +11 -8
  127. package/dist/saves.d.ts +96 -0
  128. package/dist/saves.d.ts.map +1 -0
  129. package/dist/saves.js +999 -0
  130. package/dist/search.d.ts +12 -63
  131. package/dist/search.d.ts.map +1 -1
  132. package/dist/search.js +9 -52
  133. package/dist/seed.d.ts +16 -2
  134. package/dist/seed.d.ts.map +1 -1
  135. package/dist/seed.js +66 -0
  136. package/dist/socket/admission.d.ts +7 -0
  137. package/dist/socket/admission.d.ts.map +1 -0
  138. package/dist/socket/admission.js +39 -0
  139. package/dist/socket/answers.d.ts +6 -0
  140. package/dist/socket/answers.d.ts.map +1 -0
  141. package/dist/socket/answers.js +20 -0
  142. package/dist/socket/reads.d.ts +10 -0
  143. package/dist/socket/reads.d.ts.map +1 -0
  144. package/dist/socket/reads.js +31 -0
  145. package/dist/socket.d.ts +43 -0
  146. package/dist/socket.d.ts.map +1 -0
  147. package/dist/socket.js +96 -0
  148. package/dist/source.d.ts +61 -0
  149. package/dist/source.d.ts.map +1 -0
  150. package/dist/source.js +81 -0
  151. package/dist/status.d.ts +0 -2
  152. package/dist/status.d.ts.map +1 -1
  153. package/dist/status.js +0 -6
  154. package/dist/store/fences.d.ts +16 -0
  155. package/dist/store/fences.d.ts.map +1 -0
  156. package/dist/store/fences.js +26 -0
  157. package/dist/store/installs.d.ts +30 -0
  158. package/dist/store/installs.d.ts.map +1 -0
  159. package/dist/store/installs.js +34 -0
  160. package/dist/store/records.d.ts +212 -0
  161. package/dist/store/records.d.ts.map +1 -0
  162. package/dist/store/records.js +408 -0
  163. package/dist/store/room-held.d.ts +8 -0
  164. package/dist/store/room-held.d.ts.map +1 -0
  165. package/dist/store/room-held.js +17 -0
  166. package/dist/store/watchers.d.ts +15 -0
  167. package/dist/store/watchers.d.ts.map +1 -0
  168. package/dist/store/watchers.js +36 -0
  169. package/dist/store/writes.d.ts +24 -0
  170. package/dist/store/writes.d.ts.map +1 -0
  171. package/dist/store/writes.js +125 -0
  172. package/dist/store.d.ts +78 -145
  173. package/dist/store.d.ts.map +1 -1
  174. package/dist/store.js +117 -367
  175. package/dist/stored.d.ts +58 -32
  176. package/dist/stored.d.ts.map +1 -1
  177. package/dist/stored.js +264 -187
  178. package/dist/tables.d.ts +107 -25
  179. package/dist/tables.d.ts.map +1 -1
  180. package/dist/tables.js +193 -47
  181. package/dist/tallies.d.ts +18 -0
  182. package/dist/tallies.d.ts.map +1 -0
  183. package/dist/tallies.js +129 -0
  184. package/dist/validation.d.ts.map +1 -1
  185. package/dist/validation.js +7 -4
  186. package/dist/windows.d.ts +1 -12
  187. package/dist/windows.d.ts.map +1 -1
  188. package/dist/windows.js +2 -75
  189. package/dist/wire.d.ts +15 -4
  190. package/dist/wire.d.ts.map +1 -1
  191. package/dist/wire.js +6 -1
  192. package/package.json +2 -2
  193. package/dist/fence.d.ts +0 -6
  194. package/dist/fence.d.ts.map +0 -1
  195. package/dist/fence.js +0 -10
  196. package/dist/fulltext.d.ts +0 -27
  197. package/dist/fulltext.d.ts.map +0 -1
  198. package/dist/fulltext.js +0 -370
  199. package/dist/words.d.ts +0 -19
  200. package/dist/words.d.ts.map +0 -1
  201. package/dist/words.js +0 -156
@@ -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
6
  import { changeBatchIdentity, chatDraftRevision } from './identity.js';
5
7
  import { OFFLINE } from './outcome.js';
8
+ import { jobsOf, runningLine } from './parts.js';
9
+ import { openQuestion } from './questions.js';
6
10
  import { untilAnswered } from './recovery.js';
7
- import { capped, cursorOf, decodedCursor, FROM_START, messageOrder, precedes } from './windows.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,27 +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();
261
274
  const unlisted = new Set();
262
275
  let listKnown = false;
263
- let listHeld = false;
264
- let listKept = false;
276
+ let listOwed = false;
277
+ let listTelling = false;
278
+ let rowTimer = null;
279
+ const closing = new AbortController();
265
280
  const agentWatchers = new Set();
266
281
  const unnamed = new Set();
267
- let agentsKnown = false;
268
- let agentNames = new Map();
269
- let retiredAgents = new Set();
270
- let agentsRead = null;
271
- let agentsFrom;
282
+ let agentsShown = null;
283
+ let listedAgents = null;
284
+ let agentsReading = false;
272
285
  let replicated = new Set();
273
286
  let handedReplicated;
274
287
  function liveOf(conversation) {
275
288
  return lives.get(conversation) ?? QUIET;
276
289
  }
277
- function viewOf(conversation, held, frame) {
278
- const draft = drafts.get(conversation);
290
+ function viewOf(conversation, held, frame, draft, ended, marked) {
279
291
  const facts = liveOf(conversation);
280
292
  return {
281
293
  conversation,
@@ -289,14 +301,17 @@ export function createConversationStore(deps) {
289
301
  turn: structuredClone(facts.turn),
290
302
  questions: structuredClone(facts.questions),
291
303
  process: facts.process === null ? null : { ...facts.process },
304
+ processEnded: ended,
292
305
  commands: structuredClone(facts.commands),
293
306
  options: structuredClone(facts.options),
294
307
  starting: facts.starting,
308
+ subagents: structuredClone(facts.subagents),
295
309
  link: facts.link === null ? null : { ...facts.link },
296
310
  deliveryUnknown: [...facts.deliveryUnknown],
297
- chatDraft: draft === undefined || unwritten(draft)
298
- ? null
299
- : { 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),
300
315
  };
301
316
  }
302
317
  function emit(conversation) {
@@ -308,7 +323,7 @@ export function createConversationStore(deps) {
308
323
  const ready = [...view.watchers].filter((watcher) => watcher.ready);
309
324
  if (ready.length === 0)
310
325
  continue;
311
- 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);
312
327
  for (const watcher of ready)
313
328
  watcher.notify(value);
314
329
  }
@@ -318,11 +333,13 @@ export function createConversationStore(deps) {
318
333
  }
319
334
  async function slide(local, conversation, arriving) {
320
335
  let count;
336
+ let marked;
321
337
  for (const view of views.values()) {
322
338
  if (view.conversation !== conversation || view.window === null)
323
339
  continue;
324
340
  if (arriving === null) {
325
341
  view.window = [];
342
+ view.marked = [];
326
343
  view.more = false;
327
344
  if (view.total !== null)
328
345
  view.total = 0;
@@ -332,6 +349,10 @@ export function createConversationStore(deps) {
332
349
  const next = windowOf(view.window, joining, view.asked.limit);
333
350
  view.window = next.messages;
334
351
  view.more ||= next.dropped;
352
+ if (arriving.length > 0) {
353
+ marked ??= await local.jobMarkers(conversation);
354
+ view.marked = marked;
355
+ }
335
356
  if (view.total !== null && arriving.length > 0) {
336
357
  count ??= await local.messageCount(conversation);
337
358
  view.total = count;
@@ -344,148 +365,234 @@ export function createConversationStore(deps) {
344
365
  return 'asking';
345
366
  return facts.turn === null ? 'idle' : 'working';
346
367
  }
347
- function entries() {
348
- 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) => ({
349
386
  conversation: entry.conversation,
350
- agent: { identity: entry.agent, name: agentNames.get(entry.agent) ?? null },
387
+ agent: { identity: entry.agent, name: entry.agentName },
351
388
  name: entry.name,
352
389
  created: entry.created,
353
390
  finished: entry.finished,
354
391
  outcome: entry.outcome,
355
392
  waiting: entry.waiting,
356
- state: stateOf(entry.conversation),
357
- started: lives.get(entry.conversation)?.turn?.started ?? null,
393
+ ...rowOf(entry.conversation),
394
+ ...heldFacts(rows.get(entry.conversation), entry),
358
395
  }));
359
396
  }
360
- function keepList() {
361
- if (!listKnown || listKept)
362
- return;
363
- listKept = true;
364
- queueMicrotask(() => {
365
- listKept = false;
366
- if (deps.closed())
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)
367
434
  return;
368
- const entries = [...listing.values()];
369
- void deps.durably((local) => local.fact('conversationList', entries)).catch(() => { });
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);
370
471
  });
371
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
+ }
372
512
  function emitList() {
373
513
  if (deps.closed())
374
514
  return;
375
- if (listWatchers.size > 0) {
376
- const value = entries();
377
- for (const watcher of listWatchers)
378
- watcher(value);
379
- }
515
+ tellList();
516
+ emitAgents();
380
517
  learned();
381
518
  }
382
- function unlearnable() {
383
- return !deps.online() || (deps.listed() && deps.agents() === undefined);
384
- }
385
519
  function learned() {
386
520
  if (deps.closed())
387
521
  return;
388
- const named = agentsKnown || unlearnable();
389
- if (unlisted.size > 0 &&
390
- named &&
391
- (listKnown || listHeld || deps.stream() === undefined || unlearnable())) {
392
- const value = entries();
393
- for (const watcher of [...unlisted]) {
394
- unlisted.delete(watcher);
395
- listWatchers.add(watcher);
396
- watcher(value);
397
- }
398
- }
399
- if (unnamed.size > 0 && named) {
400
- const value = agents();
522
+ if (unlisted.size > 0)
523
+ tellList();
524
+ if (unnamed.size === 0)
525
+ return;
526
+ shownList((list) => {
527
+ const value = agents(list);
401
528
  for (const watcher of [...unnamed]) {
402
529
  unnamed.delete(watcher);
403
530
  agentWatchers.add(watcher);
404
531
  watcher(value);
405
532
  }
406
- }
533
+ });
407
534
  }
408
- function agents() {
409
- return [...agentNames]
410
- .filter(([identity]) => !retiredAgents.has(identity))
411
- .map(([identity, name]) => ({ identity, name }))
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 }))
412
545
  .sort((left, right) => left.name === right.name
413
546
  ? left.identity.localeCompare(right.identity)
414
547
  : left.name.localeCompare(right.name));
415
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
+ }
416
566
  function emitAgents() {
417
567
  if (deps.closed() || agentWatchers.size === 0)
418
568
  return;
419
- const value = agents();
420
- for (const watcher of agentWatchers)
421
- 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
+ });
422
578
  }
423
579
  function setLive(conversation, next) {
424
- const before = stateOf(conversation);
425
- const began = lives.get(conversation)?.turn?.started;
580
+ const before = rowOf(conversation);
426
581
  if (next === null)
427
582
  lives.delete(conversation);
428
583
  else
429
584
  lives.set(conversation, next);
430
- if (!listing.has(conversation))
431
- return;
432
- if (stateOf(conversation) !== before || next?.turn?.started !== began)
433
- emitList();
434
- }
435
- async function readAgents() {
436
- const declaration = deps.agents();
437
- agentsFrom = JSON.stringify(declaration);
438
- if (declaration === undefined)
439
- return;
440
- const names = new Map();
441
- const retired = new Set();
442
- let covered = null;
443
- for (let after;;) {
444
- const read = await deps.documents(declaration, after);
445
- if ('pending' in read)
446
- return;
447
- const coverage = JSON.stringify(read.coverage);
448
- if (covered !== null && coverage !== covered)
449
- return readAgents();
450
- covered = coverage;
451
- for (const document of read.documents) {
452
- const identity = document.fields?.[declaration.identity];
453
- const name = document.fields?.[declaration.name];
454
- if (typeof identity !== 'string' || typeof name !== 'string')
455
- continue;
456
- names.set(identity, name);
457
- const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
458
- if (ended !== undefined && ended !== null)
459
- retired.add(identity);
460
- }
461
- if (read.next === undefined)
462
- break;
463
- after = read.next;
464
- }
465
- agentsKnown = true;
466
- const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
467
- if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents])) {
468
- learned();
469
- return;
470
- }
471
- agentNames = names;
472
- retiredAgents = retired;
473
- emitAgents();
474
- if (named)
585
+ const after = rowOf(conversation);
586
+ if (after.state !== before.state || after.started !== before.started)
475
587
  emitList();
476
- learned();
477
- const ended = [...drafts.keys()].filter(retiredChat);
478
- if (ended.length > 0)
479
- await remove(ended);
588
+ else if (after.line !== before.line || after.question !== before.question)
589
+ tellSoon();
480
590
  }
481
- function retiredChat(conversation) {
482
- return (conversation.startsWith(NEW_CHAT) && retiredAgents.has(conversation.slice(NEW_CHAT.length)));
483
- }
484
- function agentsReady() {
485
- agentsRead ??= readAgents().catch(() => {
486
- agentsRead = null;
487
- });
488
- 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);
489
596
  }
490
597
  function live(conversation, change) {
491
598
  const owed = unsent.get(conversation);
@@ -508,6 +615,7 @@ export function createConversationStore(deps) {
508
615
  commands: facts.has('commands') ? QUIET.commands : held.commands,
509
616
  options: facts.has('options') ? QUIET.options : held.options,
510
617
  starting: facts.has('starting') ? QUIET.starting : held.starting,
618
+ subagents: facts.has('subagents') ? QUIET.subagents : held.subagents,
511
619
  link: facts.has('link') ? QUIET.link : held.link,
512
620
  deliveryUnknown: facts.has('deliveryUnknown')
513
621
  ? QUIET.deliveryUnknown
@@ -542,17 +650,23 @@ export function createConversationStore(deps) {
542
650
  }
543
651
  async function remove(leaving) {
544
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
+ ]);
545
659
  await local.dropConversations(leaving);
546
660
  const changed = [];
547
661
  for (const conversation of leaving) {
548
- if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
662
+ if (kept.has(conversation) || lives.has(conversation))
549
663
  changed.push(conversation);
664
+ for (const view of views.values()) {
665
+ if (view.conversation === conversation)
666
+ view.draft = null;
550
667
  }
551
- stored.delete(conversation);
552
- reads.delete(conversation);
553
- partials.delete(conversation);
554
- drafts.delete(conversation);
555
668
  behind.delete(conversation);
669
+ heardNewest.delete(conversation);
556
670
  unsent.delete(conversation);
557
671
  setLive(conversation, null);
558
672
  await slide(local, conversation, null);
@@ -561,19 +675,68 @@ export function createConversationStore(deps) {
561
675
  deps.publish(conversationChangeSet(changed));
562
676
  });
563
677
  }
564
- function missed(conversation) {
565
- const read = reads.get(conversation);
566
- const newest = listing.get(conversation)?.newest;
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);
567
689
  return (read === undefined ||
568
690
  newest === undefined ||
569
- partials.has(conversation) ||
691
+ held?.partial === true ||
570
692
  (newest !== null && (read === null || newest > read)));
571
693
  }
572
- function noted(conversation, message) {
573
- const entry = listing.get(conversation);
574
- if (entry === undefined || (entry.newest !== null && entry.newest >= message.position))
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))
575
699
  return;
576
- listing.set(conversation, { ...entry, newest: message.position });
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
+ }
577
740
  }
578
741
  function gap(conversation) {
579
742
  gaps.set(conversation, (gaps.get(conversation) ?? 0) + 1);
@@ -583,7 +746,7 @@ export function createConversationStore(deps) {
583
746
  return deps.durably(async (local) => {
584
747
  if (!replicated.has(conversation))
585
748
  return;
586
- const read = reads.get(conversation);
749
+ const read = (await readsOf(local, [conversation])).get(conversation)?.read;
587
750
  const advances = read !== undefined &&
588
751
  !behind.has(conversation) &&
589
752
  (read === null || message.position > read);
@@ -593,23 +756,22 @@ export function createConversationStore(deps) {
593
756
  messages: [message],
594
757
  ...(advances ? { read: { at: message.position, partial: false } } : {}),
595
758
  });
596
- stored.add(conversation);
597
- if (advances)
598
- reads.set(conversation, message.position);
599
759
  sealTurns(conversation, new Set([message.turn]));
600
760
  await slide(local, conversation, [message]);
601
761
  deps.publish(conversationChangeSet([conversation]));
762
+ tellList();
602
763
  });
603
764
  }
604
765
  async function readHistories(conversations, ended) {
605
766
  const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
767
+ const held = await readsOf(await deps.local(), conversations);
606
768
  let waiting = conversations.map((conversation) => ({
607
769
  conversation,
608
- after: reads.get(conversation) ?? undefined,
770
+ after: held.get(conversation)?.read ?? undefined,
609
771
  }));
610
772
  while (waiting.length > 0) {
611
773
  const asking = waiting.slice(0, HISTORY_CONVERSATIONS);
612
- const result = await deps.call('conversation.history', {
774
+ const result = await deps.history({
613
775
  conversations: asking.map(({ conversation, after }) => ({
614
776
  conversation,
615
777
  ...(after === undefined ? {} : { after }),
@@ -661,15 +823,10 @@ export function createConversationStore(deps) {
661
823
  return;
662
824
  const whole = next === undefined;
663
825
  const cursor = next ?? newestOf(arriving, from);
664
- 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);
665
828
  const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
666
829
  await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
667
- stored.add(conversation);
668
- reads.set(conversation, cursor);
669
- if (partial)
670
- partials.add(conversation);
671
- else
672
- partials.delete(conversation);
673
830
  sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
674
831
  if (whole && (gaps.get(conversation) ?? 0) === generation)
675
832
  behind.delete(conversation);
@@ -677,6 +834,8 @@ export function createConversationStore(deps) {
677
834
  if (arriving.length > 0 || sealing || whole) {
678
835
  deps.publish(conversationChangeSet([conversation]));
679
836
  }
837
+ if (arriving.length > 0)
838
+ tellSoon();
680
839
  });
681
840
  }
682
841
  function heard(event) {
@@ -712,6 +871,10 @@ export function createConversationStore(deps) {
712
871
  live(conversation, { starting: event.step });
713
872
  return;
714
873
  }
874
+ if (event.kind === 'subagents') {
875
+ live(conversation, { subagents: structuredClone(event.subagents) });
876
+ return;
877
+ }
715
878
  if (event.kind === 'link') {
716
879
  live(conversation, { link: { connected: event.connected } });
717
880
  return;
@@ -739,17 +902,24 @@ export function createConversationStore(deps) {
739
902
  emit(conversation);
740
903
  }
741
904
  async function kept(local, conversation, following) {
742
- if (following === null || empty(following)) {
905
+ const shown = following === null || empty(following) ? null : following;
906
+ if (shown === null)
743
907
  await local.dropChatDraft(conversation);
744
- drafts.delete(conversation);
745
- }
746
- else {
747
- await local.keepChatDrafts([following]);
748
- 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;
749
916
  }
750
917
  }
918
+ async function draftAt(local, conversation) {
919
+ return (await local.chatDraft(conversation)) ?? undefined;
920
+ }
751
921
  async function sealed(local, conversation, turns) {
752
- const held = drafts.get(conversation);
922
+ const held = await draftAt(local, conversation);
753
923
  const submitted = held?.submitted ?? [];
754
924
  const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
755
925
  if (held === undefined || left.length === submitted.length)
@@ -760,8 +930,8 @@ export function createConversationStore(deps) {
760
930
  function moved(agent, operation, to) {
761
931
  const from = newChat(agent);
762
932
  return deps.durably(async (local) => {
763
- const held = drafts.get(from);
764
- const earlier = (held?.moves ?? []).filter((one) => !deps.expired(one));
933
+ const held = await draftAt(local, from);
934
+ const earlier = held?.moves ?? [];
765
935
  if (earlier.includes(operation))
766
936
  return;
767
937
  const blank = {
@@ -770,18 +940,20 @@ export function createConversationStore(deps) {
770
940
  revision: chatDraftRevision(),
771
941
  };
772
942
  const { moves: _answered, ...unsent } = held ?? blank;
773
- 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 }];
774
946
  const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
775
947
  await local.keepChatDrafts([left, ...taken]);
776
- drafts.set(from, left);
948
+ shownDraft(from, left);
777
949
  for (const one of taken)
778
- drafts.set(to, one);
950
+ shownDraft(to, one);
779
951
  deps.publish(conversationChangeSet([from, to]));
780
952
  });
781
953
  }
782
954
  function withDraft(conversation, next) {
783
955
  return deps.durably(async (local) => {
784
- const following = await next(drafts.get(conversation), local);
956
+ const following = await next(await draftAt(local, conversation), local);
785
957
  if (following === undefined)
786
958
  return;
787
959
  await kept(local, conversation, following);
@@ -790,10 +962,8 @@ export function createConversationStore(deps) {
790
962
  }
791
963
  async function submitted(conversation, operation, draft) {
792
964
  let submission;
793
- let earlier = false;
794
965
  await withDraft(conversation, (current) => {
795
966
  submission = current?.submitted?.find((one) => one.operation === operation);
796
- earlier = submission !== undefined;
797
967
  if (submission !== undefined)
798
968
  return undefined;
799
969
  const said = draft ?? current;
@@ -806,23 +976,15 @@ export function createConversationStore(deps) {
806
976
  submitted: [...(current?.submitted ?? []), submission],
807
977
  };
808
978
  });
809
- return { submission: submission, earlier };
810
- }
811
- function forgotten(conversation, submission) {
812
- return withDraft(conversation, (current) => {
813
- const held = current?.submitted ?? [];
814
- if (current === undefined || !held.includes(submission))
815
- return undefined;
816
- return withSubmitted(current, held.filter((one) => one !== submission));
817
- });
979
+ return submission;
818
980
  }
819
981
  async function dispatched(conversation, submission) {
820
982
  const dispatch = { answered: false };
821
983
  sending.set(submission.operation, dispatch);
822
984
  try {
823
- const result = await invoke('conversation.send', conversation, {
824
- text: submission.text,
985
+ const result = await invoke('conversations/send', conversation, {
825
986
  operation_id: submission.operation,
987
+ text: submission.text,
826
988
  });
827
989
  dispatch.answered = result.kind !== 'transport-uncertainty';
828
990
  if (result.kind === 'revoked-authorization')
@@ -831,7 +993,7 @@ export function createConversationStore(deps) {
831
993
  deps.unanswered();
832
994
  return result;
833
995
  }
834
- const turn = answered(result, 'turn');
996
+ const turn = answered(result, 'message_id');
835
997
  await withDraft(conversation, async (current, local) => {
836
998
  const held = current?.submitted?.find((one) => one.operation === submission.operation);
837
999
  if (current === undefined || held === undefined || held.turn !== null)
@@ -851,12 +1013,11 @@ export function createConversationStore(deps) {
851
1013
  sending.delete(submission.operation);
852
1014
  }
853
1015
  }
854
- function invoke(name, conversation, extra = {}) {
855
- return deps.call(name, { conversation, ...extra });
1016
+ function invoke(name, conversation, extra = { operation_id: changeBatchIdentity() }) {
1017
+ return deps.call(name, { conversation_id: conversation, ...extra });
856
1018
  }
857
1019
  const surface = {
858
- async list() {
859
- await agentsReady();
1020
+ list() {
860
1021
  return new Promise((settle) => {
861
1022
  const answer = (listed) => {
862
1023
  unlisted.delete(answer);
@@ -868,20 +1029,25 @@ export function createConversationStore(deps) {
868
1029
  });
869
1030
  },
870
1031
  async read(conversation, options = {}) {
871
- const asked = askedOf(options);
1032
+ const { text, ...windowed } = options;
1033
+ const asked = askedOf(windowed);
1034
+ const phrase = phraseOf(text);
872
1035
  return deps.durably(async (local) => {
873
- const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
874
- const total = asked.total ? await local.messageCount(conversation) : null;
875
- 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));
876
1040
  });
877
1041
  },
878
1042
  async create(input) {
879
- const result = await deps.call('conversation.create', input);
880
- const conversation = answered(result, 'conversation');
1043
+ const result = await deps.call('conversations/create', input);
1044
+ const conversation = answered(result, 'conversation_id');
881
1045
  if (conversation === null)
882
1046
  return result;
883
- if (!listing.has(conversation))
1047
+ const listed = (await heldList()) ?? [];
1048
+ if (!listed.some((entry) => entry.conversation === conversation)) {
884
1049
  creations.add(conversation);
1050
+ }
885
1051
  const creation = creationOf(input);
886
1052
  if (creation !== null)
887
1053
  await moved(creation.agent, creation.operation, conversation);
@@ -898,11 +1064,7 @@ export function createConversationStore(deps) {
898
1064
  });
899
1065
  if (held === null)
900
1066
  return DURABLE_WRITE;
901
- const { submission, earlier } = held;
902
- if (!earlier || !deps.expired(operation))
903
- return dispatched(conversation, submission);
904
- await forgotten(conversation, submission);
905
- return EXPIRED;
1067
+ return dispatched(conversation, held);
906
1068
  }
907
1069
  if (draft !== undefined) {
908
1070
  await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
@@ -912,28 +1074,19 @@ export function createConversationStore(deps) {
912
1074
  return OFFLINE;
913
1075
  },
914
1076
  answer(conversation, answer) {
915
- return invoke('conversation.answer', conversation, { answer });
1077
+ return invoke('conversations/interaction/answer', conversation, {
1078
+ operation_id: changeBatchIdentity(),
1079
+ ...answer,
1080
+ });
916
1081
  },
917
1082
  interrupt(conversation) {
918
- return invoke('conversation.interrupt', conversation);
1083
+ return invoke('conversations/interrupt', conversation);
919
1084
  },
920
1085
  kill(conversation) {
921
- return invoke('conversation.kill', conversation);
922
- },
923
- async cancel(conversation, turn) {
924
- const result = await invoke('conversation.cancel', conversation, { turn });
925
- if (result.kind !== 'action')
926
- return result;
927
- await withDraft(conversation, (current) => {
928
- const held = current?.submitted?.find((one) => one.turn === turn);
929
- if (current === undefined || held === undefined)
930
- return undefined;
931
- return returned(current, held, current.submitted.filter((one) => one !== held));
932
- });
933
- return result;
1086
+ return invoke('conversations/kill', conversation);
934
1087
  },
935
1088
  delete(conversation) {
936
- return invoke('conversation.delete', conversation);
1089
+ return invoke('conversations/delete', conversation);
937
1090
  },
938
1091
  };
939
1092
  const chatDraft = {
@@ -962,41 +1115,40 @@ export function createConversationStore(deps) {
962
1115
  return {
963
1116
  surface,
964
1117
  chatDraft,
965
- hydrate(held, kept, list) {
966
- if (list !== null && !listKnown) {
967
- listing.clear();
968
- for (const entry of list)
969
- listing.set(entry.conversation, { ...entry });
970
- listHeld = true;
971
- }
972
- stored.clear();
973
- reads.clear();
974
- partials.clear();
975
- for (const entry of held) {
976
- stored.add(entry.conversation);
977
- if (entry.read !== undefined)
978
- reads.set(entry.conversation, entry.read);
979
- if (entry.partial)
980
- partials.add(entry.conversation);
981
- }
982
- drafts.clear();
983
- for (const record of kept)
984
- drafts.set(record.conversation, record);
985
- if (listHeld)
986
- learned();
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();
987
1124
  },
988
1125
  async reconcile() {
989
1126
  if (!listKnown)
990
1127
  return;
991
- 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]));
992
1138
  const leaving = [
993
- ...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
1139
+ ...new Set([
1140
+ ...replicated,
1141
+ ...reads.keys(),
1142
+ ...kept.map(({ conversation }) => conversation),
1143
+ ]),
994
1144
  ].filter((conversation) => !named.has(conversation) &&
995
1145
  !creations.has(conversation) &&
996
- (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
1146
+ (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation, list)));
997
1147
  for (const conversation of named) {
998
- if (!replicated.has(conversation) && missed(conversation))
1148
+ if (!replicated.has(conversation) &&
1149
+ missed(conversation, reads.get(conversation), entries.get(conversation))) {
999
1150
  behind.add(conversation);
1151
+ }
1000
1152
  }
1001
1153
  replicated = named;
1002
1154
  handReplicated();
@@ -1027,16 +1179,16 @@ export function createConversationStore(deps) {
1027
1179
  progress.complete = false;
1028
1180
  },
1029
1181
  async resend(progress) {
1030
- for (const [conversation, held] of [...drafts]) {
1031
- for (const submission of held.submitted ?? []) {
1032
- if (submission.turn !== null || sending.has(submission.operation))
1033
- 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;
1034
1189
  if (deps.closed())
1035
1190
  return;
1036
- if (deps.expired(submission.operation)) {
1037
- await forgotten(conversation, submission);
1038
- continue;
1039
- }
1191
+ tried.add(submission.operation);
1040
1192
  const result = await dispatched(conversation, submission);
1041
1193
  if (result.kind === 'revoked-authorization')
1042
1194
  return;
@@ -1058,6 +1210,7 @@ export function createConversationStore(deps) {
1058
1210
  return listKnown;
1059
1211
  },
1060
1212
  reopened() {
1213
+ relisting = null;
1061
1214
  for (const [operation, dispatch] of sending)
1062
1215
  if (!dispatch.answered)
1063
1216
  sending.delete(operation);
@@ -1073,6 +1226,9 @@ export function createConversationStore(deps) {
1073
1226
  asked,
1074
1227
  watchers: new Set(),
1075
1228
  window: null,
1229
+ draft: null,
1230
+ ended: null,
1231
+ marked: [],
1076
1232
  more: false,
1077
1233
  total: null,
1078
1234
  };
@@ -1084,7 +1240,10 @@ export function createConversationStore(deps) {
1084
1240
  if (view.window !== null)
1085
1241
  return;
1086
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);
1087
1245
  view.window = messages;
1246
+ view.marked = await local.jobMarkers(conversation);
1088
1247
  view.more = more;
1089
1248
  view.total = asked.total ? await local.messageCount(conversation) : null;
1090
1249
  }), reading.signal)
@@ -1093,7 +1252,7 @@ export function createConversationStore(deps) {
1093
1252
  return;
1094
1253
  watcher.ready = true;
1095
1254
  const window = view.window ?? [];
1096
- watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
1255
+ watcher.notify(viewOf(conversation, window, sharedFrame(view, window), view.draft, view.ended, view.marked));
1097
1256
  })
1098
1257
  .catch(() => { });
1099
1258
  return () => {
@@ -1105,91 +1264,66 @@ export function createConversationStore(deps) {
1105
1264
  };
1106
1265
  },
1107
1266
  watchList(deliver) {
1108
- let ended = false;
1109
- void agentsReady().then(() => {
1110
- if (ended)
1111
- return;
1112
- unlisted.add(deliver);
1113
- learned();
1114
- });
1267
+ unlisted.add(deliver);
1268
+ learned();
1115
1269
  return () => {
1116
- ended = true;
1117
1270
  unlisted.delete(deliver);
1118
1271
  listWatchers.delete(deliver);
1119
1272
  };
1120
1273
  },
1121
1274
  watchAgents(deliver) {
1122
1275
  unnamed.add(deliver);
1123
- void agentsReady().then(learned);
1276
+ learned();
1277
+ readAgents();
1124
1278
  return () => {
1125
1279
  unnamed.delete(deliver);
1126
1280
  agentWatchers.delete(deliver);
1127
1281
  };
1128
1282
  },
1129
1283
  learned,
1130
- declared() {
1131
- if (JSON.stringify(deps.agents()) === agentsFrom)
1132
- return;
1133
- agentsRead = readAgents().catch(() => {
1134
- agentsRead = null;
1135
- });
1136
- },
1137
1284
  changed(set) {
1138
1285
  for (const conversation of set.conversations)
1139
1286
  emit(conversation);
1140
- const declaration = deps.agents();
1141
- if (declaration !== undefined &&
1142
- set.documents.some((change) => change.locator === declaration.locator)) {
1143
- agentsRead = readAgents().catch(() => {
1144
- agentsRead = null;
1145
- });
1146
- }
1147
1287
  },
1148
1288
  receive(event) {
1149
1289
  if (deps.closed())
1150
- return false;
1151
- if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
1152
- if (event.kind === 'list') {
1153
- listing.clear();
1154
- for (const entry of event.entries) {
1155
- listing.set(entry.conversation, { ...entry });
1156
- creations.delete(entry.conversation);
1157
- }
1158
- listKnown = true;
1159
- for (const conversation of replicated) {
1160
- if (!listing.has(conversation))
1161
- continue;
1162
- if (!missed(conversation))
1163
- behind.delete(conversation);
1164
- else if (!behind.has(conversation))
1165
- gap(conversation);
1166
- }
1167
- openingEnded();
1168
- }
1169
- else if (event.kind === 'listed') {
1170
- listing.set(event.entry.conversation, { ...event.entry });
1171
- creations.delete(event.entry.conversation);
1172
- }
1173
- else {
1174
- 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 });
1175
1297
  }
1176
- keepList();
1177
- emitList();
1178
- return (event.kind !== 'list' &&
1179
- listKnown &&
1180
- (listing.size !== replicated.size ||
1181
- [...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;
1182
1319
  }
1183
1320
  heard(event);
1184
- return false;
1185
- },
1186
- searchable() {
1187
- return [...(listKnown ? listing.keys() : stored)].sort();
1188
- },
1189
- acquired(conversation) {
1190
- return reads.has(conversation) && !partials.has(conversation);
1191
1321
  },
1192
1322
  close() {
1323
+ closing.abort();
1324
+ if (rowTimer !== null)
1325
+ clearTimeout(rowTimer);
1326
+ rowTimer = null;
1193
1327
  views.clear();
1194
1328
  lives.clear();
1195
1329
  unsent.clear();