@agentshouse/mdruntime 0.1.0-cfb2ae0107054 → 0.1.0-cfcbb238a5fa0

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 (124) hide show
  1. package/AGENTS.md +262 -262
  2. package/dist/acquisition.d.ts +14 -0
  3. package/dist/acquisition.d.ts.map +1 -0
  4. package/dist/acquisition.js +24 -0
  5. package/dist/adapter.d.ts +14 -7
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/bootstrap.d.ts +2 -1
  8. package/dist/bootstrap.d.ts.map +1 -1
  9. package/dist/bootstrap.js +4 -0
  10. package/dist/change-set.d.ts +2 -0
  11. package/dist/change-set.d.ts.map +1 -1
  12. package/dist/change-set.js +36 -10
  13. package/dist/chat/kit.d.ts +2 -1
  14. package/dist/chat/kit.d.ts.map +1 -1
  15. package/dist/chat/kit.js +3 -3
  16. package/dist/chat/list.d.ts +2 -1
  17. package/dist/chat/list.d.ts.map +1 -1
  18. package/dist/chat/list.js +4 -3
  19. package/dist/chat/message.d.ts.map +1 -1
  20. package/dist/chat/message.js +2 -2
  21. package/dist/chat/messages.d.ts +1 -0
  22. package/dist/chat/messages.d.ts.map +1 -1
  23. package/dist/chat/messages.js +4 -0
  24. package/dist/chat/provider.d.ts +1 -1
  25. package/dist/chat/provider.d.ts.map +1 -1
  26. package/dist/chat/provider.js +26 -17
  27. package/dist/chat/ready.d.ts +1 -0
  28. package/dist/chat/ready.d.ts.map +1 -1
  29. package/dist/chat/ready.js +2 -2
  30. package/dist/client.d.ts +10 -6
  31. package/dist/client.d.ts.map +1 -1
  32. package/dist/client.js +11 -2
  33. package/dist/collection.d.ts +17 -3
  34. package/dist/collection.d.ts.map +1 -1
  35. package/dist/collection.js +230 -19
  36. package/dist/connection.d.ts +23 -7
  37. package/dist/connection.d.ts.map +1 -1
  38. package/dist/connection.js +1065 -192
  39. package/dist/conversation.d.ts +11 -5
  40. package/dist/conversation.d.ts.map +1 -1
  41. package/dist/conversation.js +241 -100
  42. package/dist/durability.d.ts +7 -1
  43. package/dist/durability.d.ts.map +1 -1
  44. package/dist/entry.d.ts +2 -2
  45. package/dist/entry.d.ts.map +1 -1
  46. package/dist/entry.js +3 -3
  47. package/dist/host.d.ts +16 -2
  48. package/dist/host.d.ts.map +1 -1
  49. package/dist/host.js +82 -8
  50. package/dist/identity.d.ts +2 -0
  51. package/dist/identity.d.ts.map +1 -1
  52. package/dist/identity.js +6 -0
  53. package/dist/index.d.ts +8 -5
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +2 -0
  56. package/dist/memory.d.ts +7 -2
  57. package/dist/memory.d.ts.map +1 -1
  58. package/dist/memory.js +20 -7
  59. package/dist/model.d.ts +4 -2
  60. package/dist/model.d.ts.map +1 -1
  61. package/dist/model.js +10 -1
  62. package/dist/observation.d.ts +5 -2
  63. package/dist/observation.d.ts.map +1 -1
  64. package/dist/observation.js +22 -11
  65. package/dist/overlay.d.ts +2 -0
  66. package/dist/overlay.d.ts.map +1 -1
  67. package/dist/overlay.js +58 -55
  68. package/dist/query.d.ts +35 -3
  69. package/dist/query.d.ts.map +1 -1
  70. package/dist/query.js +8 -2
  71. package/dist/reach.d.ts +6 -3
  72. package/dist/reach.d.ts.map +1 -1
  73. package/dist/reach.js +16 -4
  74. package/dist/react.d.ts +7 -4
  75. package/dist/react.d.ts.map +1 -1
  76. package/dist/react.js +6 -4
  77. package/dist/read-side.d.ts +13 -6
  78. package/dist/read-side.d.ts.map +1 -1
  79. package/dist/read-side.js +285 -83
  80. package/dist/readiness.d.ts +53 -0
  81. package/dist/readiness.d.ts.map +1 -0
  82. package/dist/readiness.js +33 -0
  83. package/dist/reads.d.ts +11 -0
  84. package/dist/reads.d.ts.map +1 -0
  85. package/dist/reads.js +98 -0
  86. package/dist/recovery.d.ts +25 -0
  87. package/dist/recovery.d.ts.map +1 -0
  88. package/dist/recovery.js +197 -0
  89. package/dist/refresh.d.ts +4 -4
  90. package/dist/refresh.d.ts.map +1 -1
  91. package/dist/refresh.js +17 -10
  92. package/dist/replica.d.ts +1 -1
  93. package/dist/replica.d.ts.map +1 -1
  94. package/dist/replica.js +57 -20
  95. package/dist/room-draft.d.ts +13 -1
  96. package/dist/room-draft.d.ts.map +1 -1
  97. package/dist/room-draft.js +2 -2
  98. package/dist/rows.d.ts +5 -13
  99. package/dist/rows.d.ts.map +1 -1
  100. package/dist/rows.js +5 -13
  101. package/dist/save.d.ts +5 -0
  102. package/dist/save.d.ts.map +1 -1
  103. package/dist/save.js +36 -13
  104. package/dist/search.d.ts +36 -6
  105. package/dist/search.d.ts.map +1 -1
  106. package/dist/search.js +132 -46
  107. package/dist/seed.d.ts +16 -0
  108. package/dist/seed.d.ts.map +1 -0
  109. package/dist/seed.js +61 -0
  110. package/dist/shared.d.ts +11 -0
  111. package/dist/shared.d.ts.map +1 -0
  112. package/dist/shared.js +368 -0
  113. package/dist/status.d.ts +4 -0
  114. package/dist/status.d.ts.map +1 -1
  115. package/dist/store.d.ts +21 -5
  116. package/dist/store.d.ts.map +1 -1
  117. package/dist/store.js +157 -53
  118. package/dist/turn.d.ts +7 -0
  119. package/dist/turn.d.ts.map +1 -0
  120. package/dist/turn.js +45 -0
  121. package/dist/wire.d.ts +136 -32
  122. package/dist/wire.d.ts.map +1 -1
  123. package/dist/wire.js +48 -31
  124. package/package.json +2 -2
@@ -22,6 +22,17 @@ export function unheldView(conversation) {
22
22
  chatDraft: null,
23
23
  };
24
24
  }
25
+ export const SESSION_STARTED = 'Session started';
26
+ const NEW_CHAT = 'new:';
27
+ export function newChat(agent) {
28
+ return `${NEW_CHAT}${agent}`;
29
+ }
30
+ function creationOf(input) {
31
+ if (typeof input !== 'object' || input === null)
32
+ return null;
33
+ const { agent_id: agent, operation_id: operation } = input;
34
+ return typeof agent === 'string' && typeof operation === 'string' ? { agent, operation } : null;
35
+ }
25
36
  function startedTurn(turn, operation) {
26
37
  if (operation.op === 'begin')
27
38
  return { turn, started: operation.at, parts: [], context: null };
@@ -78,11 +89,15 @@ function ordered(left, right) {
78
89
  return left.identity < right.identity ? -1 : 1;
79
90
  return 0;
80
91
  }
81
- function merged(held, arriving) {
92
+ function windowOf(held, arriving, limit) {
82
93
  const byIdentity = new Map(held.map((message) => [message.identity, message]));
83
94
  for (const message of arriving)
84
95
  byIdentity.set(message.identity, message);
85
- return [...byIdentity.values()].sort(ordered);
96
+ const next = [...byIdentity.values()].sort(ordered);
97
+ return limit === undefined ? next : next.slice(Math.max(0, next.length - limit));
98
+ }
99
+ function newestOf(arriving, from) {
100
+ return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
86
101
  }
87
102
  function acceptedMessage(value) {
88
103
  if (typeof value !== 'object' || value === null)
@@ -141,14 +156,14 @@ function historyPages(result) {
141
156
  }
142
157
  return pages;
143
158
  }
144
- function turnOf(result) {
159
+ function answered(result, field) {
145
160
  if (result.kind !== 'action')
146
161
  return null;
147
162
  const answer = result.result;
148
163
  if (typeof answer !== 'object' || answer === null)
149
164
  return null;
150
- const record = answer;
151
- return typeof record.turn === 'string' ? record.turn : null;
165
+ const value = answer[field];
166
+ return typeof value === 'string' ? value : null;
152
167
  }
153
168
  function limitOf(options) {
154
169
  const { limit } = options;
@@ -159,11 +174,18 @@ function limitOf(options) {
159
174
  }
160
175
  return limit;
161
176
  }
177
+ const EXPIRED = { kind: 'rejected', code: 'expired', fields: {}, retryable: false };
162
178
  function unwritten(draft) {
163
179
  return draft.text === '' && draft.files.length === 0;
164
180
  }
165
181
  function pending(draft) {
166
- return draft.submitted === undefined ? {} : { submitted: draft.submitted };
182
+ return {
183
+ ...(draft.submitted === undefined ? {} : { submitted: draft.submitted }),
184
+ ...(draft.moves === undefined ? {} : { moves: draft.moves }),
185
+ };
186
+ }
187
+ function empty(draft) {
188
+ return unwritten(draft) && draft.submitted === undefined && draft.moves === undefined;
167
189
  }
168
190
  function withSubmitted(draft, submitted) {
169
191
  const { submitted: _held, ...unsent } = draft;
@@ -178,15 +200,18 @@ function returned(draft, submission, others) {
178
200
  }, others);
179
201
  }
180
202
  export function createConversationStore(deps) {
181
- const messages = new Map();
203
+ const stored = new Set();
182
204
  const reads = new Map();
205
+ const partials = new Set();
183
206
  const drafts = new Map();
184
- const sending = new Set();
207
+ const sending = new Map();
185
208
  const behind = new Set();
209
+ const catching = new Map();
186
210
  const gaps = new Map();
187
211
  const lives = new Map();
188
212
  const views = new Map();
189
213
  const listing = new Map();
214
+ const creations = new Set();
190
215
  const listWatchers = new Set();
191
216
  let listKnown = false;
192
217
  const agentWatchers = new Set();
@@ -198,22 +223,17 @@ export function createConversationStore(deps) {
198
223
  function liveOf(conversation) {
199
224
  return lives.get(conversation) ?? QUIET;
200
225
  }
201
- function shownMessages(conversation, limit) {
202
- const held = messages.get(conversation) ?? [];
203
- const shown = limit === undefined ? held : held.slice(Math.max(0, held.length - limit));
204
- return shown.map(({ identity, turn, text, message }) => ({
205
- identity,
206
- turn,
207
- text,
208
- message: structuredClone(message),
209
- }));
210
- }
211
- function viewOf(conversation, limit, shown = shownMessages(conversation, limit)) {
226
+ function viewOf(conversation, held) {
212
227
  const draft = drafts.get(conversation);
213
228
  const facts = liveOf(conversation);
214
229
  return {
215
230
  conversation,
216
- messages: shown,
231
+ messages: held.map(({ identity, turn, text, message }) => ({
232
+ identity,
233
+ turn,
234
+ text,
235
+ message,
236
+ })),
217
237
  turn: structuredClone(facts.turn),
218
238
  questions: structuredClone(facts.questions),
219
239
  process: facts.process === null ? null : { ...facts.process },
@@ -224,23 +244,30 @@ export function createConversationStore(deps) {
224
244
  : { text: draft.text, files: [...draft.files], revision: draft.revision },
225
245
  };
226
246
  }
227
- function emit(conversation, messagesChanged = true) {
247
+ function emit(conversation) {
228
248
  if (deps.closed())
229
249
  return;
230
250
  for (const view of views.values()) {
231
- if (view.conversation !== conversation)
251
+ if (view.conversation !== conversation || view.window === null)
232
252
  continue;
233
- if (messagesChanged)
234
- view.messages = null;
235
253
  const ready = [...view.watchers].filter((watcher) => watcher.ready);
236
254
  if (ready.length === 0)
237
255
  continue;
238
- view.messages ??= shownMessages(conversation, view.limit);
239
- const value = viewOf(conversation, view.limit, view.messages);
256
+ const value = viewOf(conversation, view.window);
240
257
  for (const watcher of ready)
241
258
  watcher.notify(value);
242
259
  }
243
260
  }
261
+ function slide(conversation, arriving) {
262
+ for (const view of views.values()) {
263
+ if (view.conversation !== conversation || view.window === null)
264
+ continue;
265
+ view.window = arriving === null ? [] : windowOf(view.window, arriving, view.limit);
266
+ }
267
+ }
268
+ function changesOf(conversation, arriving) {
269
+ return arriving.map(({ identity, text }) => ({ conversation, identity, text }));
270
+ }
244
271
  function stateOf(conversation) {
245
272
  const facts = liveOf(conversation);
246
273
  if (facts.questions.length > 0)
@@ -297,7 +324,10 @@ export function createConversationStore(deps) {
297
324
  const declaration = deps.agents();
298
325
  if (declaration === undefined)
299
326
  return;
300
- const { documents } = await deps.documents(declaration.locator);
327
+ const held = await deps.documents(declaration);
328
+ if ('pending' in held)
329
+ return;
330
+ const { documents } = held;
301
331
  const names = new Map();
302
332
  const retired = new Set();
303
333
  for (const document of documents) {
@@ -320,6 +350,12 @@ export function createConversationStore(deps) {
320
350
  if (named)
321
351
  emitList();
322
352
  emitAgents();
353
+ const ended = [...drafts.keys()].filter(retiredChat);
354
+ if (ended.length > 0)
355
+ await remove(ended);
356
+ }
357
+ function retiredChat(conversation) {
358
+ return (conversation.startsWith(NEW_CHAT) && retiredAgents.has(conversation.slice(NEW_CHAT.length)));
323
359
  }
324
360
  function agentsReady() {
325
361
  agentsRead ??= readAgents().catch(() => {
@@ -329,7 +365,7 @@ export function createConversationStore(deps) {
329
365
  }
330
366
  function live(conversation, change) {
331
367
  setLive(conversation, { ...liveOf(conversation), ...change });
332
- emit(conversation, false);
368
+ emit(conversation);
333
369
  }
334
370
  function relayed(conversation, turn, operation) {
335
371
  const held = liveOf(conversation).turn;
@@ -357,21 +393,25 @@ export function createConversationStore(deps) {
357
393
  }
358
394
  async function remove(leaving) {
359
395
  await deps.durably(async (local) => {
360
- await local.dropConversations(leaving);
361
396
  const removed = [];
362
- const changed = [];
363
397
  for (const conversation of leaving) {
364
- for (const message of messages.get(conversation) ?? []) {
365
- removed.push({ conversation, identity: message.identity });
398
+ for (const identity of await local.messageIdentities(conversation)) {
399
+ removed.push({ conversation, identity, text: null });
366
400
  }
367
- if (messages.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
401
+ }
402
+ await local.dropConversations(leaving);
403
+ const changed = [];
404
+ for (const conversation of leaving) {
405
+ if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
368
406
  changed.push(conversation);
369
407
  }
370
- messages.delete(conversation);
408
+ stored.delete(conversation);
371
409
  reads.delete(conversation);
410
+ partials.delete(conversation);
372
411
  drafts.delete(conversation);
373
412
  behind.delete(conversation);
374
413
  setLive(conversation, null);
414
+ slide(conversation, null);
375
415
  }
376
416
  if (changed.length > 0)
377
417
  deps.publish(conversationChangeSet(removed, changed));
@@ -393,23 +433,21 @@ export function createConversationStore(deps) {
393
433
  await local.keepMessages({
394
434
  conversation,
395
435
  messages: [message],
396
- ...(advances ? { read: message.position } : {}),
436
+ ...(advances ? { read: { at: message.position, partial: false } } : {}),
397
437
  });
398
- messages.set(conversation, merged(messages.get(conversation) ?? [], [message]));
438
+ stored.add(conversation);
399
439
  if (advances)
400
440
  reads.set(conversation, message.position);
401
441
  sealTurns(conversation, new Set([message.turn]));
402
- deps.publish(conversationChangeSet([{ conversation, identity: message.identity }], [conversation]));
442
+ slide(conversation, [message]);
443
+ deps.publish(conversationChangeSet(changesOf(conversation, [message]), [conversation]));
403
444
  });
404
445
  }
405
- async function readHistories(conversations) {
446
+ async function readHistories(conversations, ended) {
406
447
  const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
407
- const starts = new Map(conversations.map((one) => [one, reads.get(one)]));
408
- const arriving = new Map();
409
- const complete = new Set();
410
448
  let asking = conversations.map((conversation) => ({
411
449
  conversation,
412
- after: starts.get(conversation) ?? undefined,
450
+ after: reads.get(conversation) ?? undefined,
413
451
  }));
414
452
  while (asking.length > 0) {
415
453
  const result = await deps.call('conversation.history', {
@@ -421,43 +459,64 @@ export function createConversationStore(deps) {
421
459
  const pages = historyPages(result);
422
460
  if (pages === null)
423
461
  return result;
424
- const asked = new Set(asking.map(({ conversation }) => conversation));
462
+ const asked = new Map(asking.map(({ conversation, after }) => [conversation, after]));
425
463
  const following = [];
426
464
  for (const page of pages) {
427
465
  if (!asked.has(page.conversation))
428
466
  continue;
429
- arriving.set(page.conversation, [
430
- ...(arriving.get(page.conversation) ?? []),
431
- ...page.messages,
432
- ]);
433
- if (page.next === undefined)
434
- complete.add(page.conversation);
435
- else
467
+ await keepPage(page.conversation, page.messages, asked.get(page.conversation) ?? null, page.next, generations.get(page.conversation) ?? 0);
468
+ if (page.next !== undefined) {
436
469
  following.push({ conversation: page.conversation, after: page.next });
470
+ }
471
+ }
472
+ for (const { conversation } of asking) {
473
+ if (!following.some((next) => next.conversation === conversation))
474
+ ended(conversation);
437
475
  }
438
476
  asking = following;
439
477
  }
440
- for (const [conversation, held] of arriving) {
441
- if (!complete.has(conversation))
442
- continue;
443
- await keepHistory(conversation, held, starts.get(conversation), generations.get(conversation) ?? 0);
444
- }
445
478
  return null;
446
479
  }
447
- function keepHistory(conversation, arriving, from, generation) {
480
+ function caughtUp(conversations) {
481
+ const ends = new Map();
482
+ for (const conversation of conversations) {
483
+ const held = new Promise((end) => ends.set(conversation, end));
484
+ catching.set(conversation, held);
485
+ void held.then(() => {
486
+ if (catching.get(conversation) === held)
487
+ catching.delete(conversation);
488
+ });
489
+ }
490
+ const ended = (conversation) => ends.get(conversation)?.();
491
+ const read = readHistories(conversations, ended);
492
+ const endAll = () => {
493
+ for (const conversation of conversations)
494
+ ended(conversation);
495
+ };
496
+ read.then(endAll, endAll);
497
+ return read;
498
+ }
499
+ function keepPage(conversation, arriving, from, next, generation) {
448
500
  return deps.durably(async (local) => {
449
501
  if (!replicated.has(conversation))
450
502
  return;
451
- const newest = arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from ?? null);
503
+ const whole = next === undefined;
504
+ const cursor = next ?? newestOf(arriving, from);
505
+ const partial = !whole && (partials.has(conversation) || !reads.has(conversation));
452
506
  const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
453
- await local.keepMessages({ conversation, messages: arriving, read: newest });
454
- messages.set(conversation, merged(messages.get(conversation) ?? [], arriving));
455
- reads.set(conversation, newest);
507
+ await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
508
+ stored.add(conversation);
509
+ reads.set(conversation, cursor);
510
+ if (partial)
511
+ partials.add(conversation);
512
+ else
513
+ partials.delete(conversation);
456
514
  sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
457
- if ((gaps.get(conversation) ?? 0) === generation)
515
+ if (whole && (gaps.get(conversation) ?? 0) === generation)
458
516
  behind.delete(conversation);
459
- if (arriving.length > 0 || sealing) {
460
- deps.publish(conversationChangeSet(arriving.map((message) => ({ conversation, identity: message.identity })), [conversation]));
517
+ slide(conversation, arriving);
518
+ if (arriving.length > 0 || sealing || whole) {
519
+ deps.publish(conversationChangeSet(changesOf(conversation, arriving), [conversation]));
461
520
  }
462
521
  });
463
522
  }
@@ -470,7 +529,7 @@ export function createConversationStore(deps) {
470
529
  if (held === undefined)
471
530
  return;
472
531
  setLive(conversation, { ...QUIET, turn: held.turn });
473
- emit(conversation, false);
532
+ emit(conversation);
474
533
  return;
475
534
  }
476
535
  if (event.kind === 'turn') {
@@ -504,12 +563,12 @@ export function createConversationStore(deps) {
504
563
  emit(conversation);
505
564
  }
506
565
  async function kept(local, conversation, following) {
507
- if (following === null || (unwritten(following) && following.submitted === undefined)) {
566
+ if (following === null || empty(following)) {
508
567
  await local.dropChatDraft(conversation);
509
568
  drafts.delete(conversation);
510
569
  }
511
570
  else {
512
- await local.keepChatDraft(following);
571
+ await local.keepChatDrafts([following]);
513
572
  drafts.set(conversation, following);
514
573
  }
515
574
  }
@@ -522,9 +581,32 @@ export function createConversationStore(deps) {
522
581
  await kept(local, conversation, withSubmitted(held, left));
523
582
  return true;
524
583
  }
584
+ function moved(agent, operation, to) {
585
+ const from = newChat(agent);
586
+ return deps.durably(async (local) => {
587
+ const held = drafts.get(from);
588
+ const earlier = (held?.moves ?? []).filter((one) => !deps.expired(one));
589
+ if (earlier.includes(operation))
590
+ return;
591
+ const blank = {
592
+ conversation: from,
593
+ text: '',
594
+ files: [],
595
+ revision: chatDraftRevision(),
596
+ };
597
+ const { moves: _answered, ...unsent } = held ?? blank;
598
+ const taken = drafts.has(to) || empty(unsent) ? [] : [{ ...unsent, conversation: to }];
599
+ const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
600
+ await local.keepChatDrafts([left, ...taken]);
601
+ drafts.set(from, left);
602
+ for (const one of taken)
603
+ drafts.set(to, one);
604
+ deps.publish(conversationChangeSet([], [from, to]));
605
+ });
606
+ }
525
607
  function withDraft(conversation, next) {
526
608
  return deps.durably(async (local) => {
527
- const following = next(drafts.get(conversation));
609
+ const following = await next(drafts.get(conversation), local);
528
610
  if (following === undefined)
529
611
  return;
530
612
  await kept(local, conversation, following);
@@ -533,8 +615,10 @@ export function createConversationStore(deps) {
533
615
  }
534
616
  async function submitted(conversation, operation, draft) {
535
617
  let submission;
618
+ let earlier = false;
536
619
  await withDraft(conversation, (current) => {
537
620
  submission = current?.submitted?.find((one) => one.operation === operation);
621
+ earlier = submission !== undefined;
538
622
  if (submission !== undefined)
539
623
  return undefined;
540
624
  const said = draft ?? current;
@@ -552,10 +636,19 @@ export function createConversationStore(deps) {
552
636
  submitted: [...(current?.submitted ?? []), submission],
553
637
  };
554
638
  });
555
- return submission;
639
+ return { submission: submission, earlier };
640
+ }
641
+ function forgotten(conversation, submission) {
642
+ return withDraft(conversation, (current) => {
643
+ const held = current?.submitted ?? [];
644
+ if (current === undefined || !held.includes(submission))
645
+ return undefined;
646
+ return withSubmitted(current, held.filter((one) => one !== submission));
647
+ });
556
648
  }
557
649
  async function dispatched(conversation, submission) {
558
- sending.add(submission.operation);
650
+ const dispatch = {};
651
+ sending.set(submission.operation, dispatch);
559
652
  try {
560
653
  const files = await Promise.all(submission.files.map(async (file) => ({ ...(await declaredFile(file)), blob: file.blob })));
561
654
  const result = await invoke('conversation.send', conversation, {
@@ -565,17 +658,19 @@ export function createConversationStore(deps) {
565
658
  });
566
659
  if (result.kind === 'revoked-authorization')
567
660
  return result;
568
- if (result.kind !== 'action' && result.retryable)
661
+ if (result.kind !== 'action' && result.retryable) {
662
+ deps.unanswered();
569
663
  return result;
570
- const turn = turnOf(result);
571
- await withDraft(conversation, (current) => {
664
+ }
665
+ const turn = answered(result, 'turn');
666
+ await withDraft(conversation, async (current, local) => {
572
667
  const held = current?.submitted?.find((one) => one.operation === submission.operation);
573
668
  if (current === undefined || held === undefined || held.turn !== null)
574
669
  return undefined;
575
670
  const others = current.submitted.filter((one) => one !== held);
576
671
  if (result.kind !== 'action')
577
672
  return returned(current, held, others);
578
- const arrived = (messages.get(conversation) ?? []).some((message) => message.turn === turn);
673
+ const arrived = turn !== null && (await local.turnArrived(conversation, turn));
579
674
  return withSubmitted(current, turn === null || arrived
580
675
  ? others
581
676
  : current.submitted.map((one) => (one === held ? { ...held, turn } : one)));
@@ -583,7 +678,8 @@ export function createConversationStore(deps) {
583
678
  return result;
584
679
  }
585
680
  finally {
586
- sending.delete(submission.operation);
681
+ if (sending.get(submission.operation) === dispatch)
682
+ sending.delete(submission.operation);
587
683
  }
588
684
  }
589
685
  function invoke(name, conversation, extra = {}) {
@@ -596,14 +692,27 @@ export function createConversationStore(deps) {
596
692
  },
597
693
  async read(conversation, options = {}) {
598
694
  const limit = limitOf(options);
599
- return deps.durably(() => Promise.resolve(viewOf(conversation, limit)));
695
+ return deps.durably(async (local) => structuredClone(viewOf(conversation, await local.messageWindow(conversation, limit))));
600
696
  },
601
- create(input) {
602
- return deps.call('conversation.create', input);
697
+ async create(input) {
698
+ const result = await deps.call('conversation.create', input);
699
+ const conversation = answered(result, 'conversation');
700
+ if (conversation === null)
701
+ return result;
702
+ if (!listing.has(conversation))
703
+ creations.add(conversation);
704
+ const creation = creationOf(input);
705
+ if (creation !== null)
706
+ await moved(creation.agent, creation.operation, conversation);
707
+ return result;
603
708
  },
604
709
  async send(conversation, operation = changeBatchIdentity(), draft) {
605
710
  if (deps.online()) {
606
- return dispatched(conversation, await submitted(conversation, operation, draft));
711
+ const { submission, earlier } = await submitted(conversation, operation, draft);
712
+ if (!earlier || !deps.expired(operation))
713
+ return dispatched(conversation, submission);
714
+ await forgotten(conversation, submission);
715
+ return EXPIRED;
607
716
  }
608
717
  if (draft !== undefined) {
609
718
  await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
@@ -644,7 +753,7 @@ export function createConversationStore(deps) {
644
753
  text: draft.text,
645
754
  files: [...draft.files],
646
755
  revision,
647
- ...(current?.submitted === undefined ? {} : { submitted: current.submitted }),
756
+ ...(current === undefined ? {} : pending(current)),
648
757
  }));
649
758
  },
650
759
  discard(conversation) {
@@ -662,18 +771,19 @@ export function createConversationStore(deps) {
662
771
  return {
663
772
  surface,
664
773
  chatDraft,
665
- hydrate(held, stored) {
666
- messages.clear();
774
+ hydrate(held, kept) {
775
+ stored.clear();
667
776
  reads.clear();
668
- for (const view of views.values())
669
- view.messages = null;
777
+ partials.clear();
670
778
  for (const entry of held) {
671
- messages.set(entry.conversation, [...entry.messages].sort(ordered));
779
+ stored.add(entry.conversation);
672
780
  if (entry.read !== undefined)
673
781
  reads.set(entry.conversation, entry.read);
782
+ if (entry.partial)
783
+ partials.add(entry.conversation);
674
784
  }
675
785
  drafts.clear();
676
- for (const record of stored)
786
+ for (const record of kept)
677
787
  drafts.set(record.conversation, record);
678
788
  },
679
789
  async reconcile() {
@@ -681,8 +791,10 @@ export function createConversationStore(deps) {
681
791
  return;
682
792
  const named = new Set(listing.keys());
683
793
  const leaving = [
684
- ...new Set([...replicated, ...messages.keys(), ...reads.keys(), ...drafts.keys()]),
685
- ].filter((conversation) => !named.has(conversation));
794
+ ...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
795
+ ].filter((conversation) => !named.has(conversation) &&
796
+ !creations.has(conversation) &&
797
+ (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
686
798
  for (const conversation of named) {
687
799
  if (!replicated.has(conversation))
688
800
  behind.add(conversation);
@@ -693,12 +805,20 @@ export function createConversationStore(deps) {
693
805
  await remove(leaving);
694
806
  },
695
807
  async catchUp(progress) {
696
- const reading = [...behind].filter((conversation) => replicated.has(conversation)).sort();
697
- if (reading.length > 0 && !deps.closed()) {
698
- const failed = await readHistories(reading);
808
+ const owed = (conversation) => behind.has(conversation) && replicated.has(conversation);
809
+ const read = async (conversations) => {
810
+ if (conversations.length === 0 || deps.closed())
811
+ return;
812
+ const failed = await caughtUp(conversations);
699
813
  if (failed?.kind === 'transport-uncertainty')
700
814
  progress.reachable = false;
701
- }
815
+ };
816
+ const asking = [...behind].filter(owed).sort();
817
+ const awaited = asking.filter((conversation) => catching.has(conversation));
818
+ await Promise.all([
819
+ read(asking.filter((conversation) => !catching.has(conversation))),
820
+ Promise.all(awaited.map((conversation) => catching.get(conversation))).then(() => read(awaited.filter((conversation) => owed(conversation) && !catching.has(conversation)))),
821
+ ]);
702
822
  if (behind.size > 0)
703
823
  progress.complete = false;
704
824
  },
@@ -709,6 +829,10 @@ export function createConversationStore(deps) {
709
829
  continue;
710
830
  if (deps.closed())
711
831
  return;
832
+ if (deps.expired(submission.operation)) {
833
+ await forgotten(conversation, submission);
834
+ continue;
835
+ }
712
836
  const result = await dispatched(conversation, submission);
713
837
  if (result.kind === 'revoked-authorization')
714
838
  return;
@@ -716,16 +840,29 @@ export function createConversationStore(deps) {
716
840
  progress.reachable = false;
717
841
  return;
718
842
  }
843
+ if (result.kind !== 'action' && result.retryable) {
844
+ progress.complete = false;
845
+ return;
846
+ }
719
847
  }
720
848
  }
721
849
  },
722
850
  current() {
723
851
  return behind.size === 0;
724
852
  },
853
+ known() {
854
+ return listKnown;
855
+ },
725
856
  reconnected() {
726
857
  for (const conversation of replicated)
727
858
  gap(conversation);
728
859
  },
860
+ reopened() {
861
+ sending.clear();
862
+ },
863
+ lost() {
864
+ catching.clear();
865
+ },
729
866
  watch(conversation, options, deliver) {
730
867
  const limit = limitOf(options);
731
868
  const key = JSON.stringify([conversation, limit ?? null]);
@@ -733,18 +870,20 @@ export function createConversationStore(deps) {
733
870
  conversation,
734
871
  limit,
735
872
  watchers: new Set(),
736
- messages: null,
873
+ window: null,
737
874
  };
738
875
  views.set(key, view);
739
876
  const watcher = { notify: deliver, ready: false };
740
877
  view.watchers.add(watcher);
741
878
  void deps
742
- .durably(() => Promise.resolve())
879
+ .durably(async (local) => {
880
+ view.window ??= await local.messageWindow(conversation, limit);
881
+ })
743
882
  .then(() => {
744
883
  if (deps.closed() || !view.watchers.has(watcher))
745
884
  return;
746
885
  watcher.ready = true;
747
- watcher.notify(viewOf(conversation, limit));
886
+ watcher.notify(viewOf(conversation, view.window ?? []));
748
887
  })
749
888
  .catch(() => { });
750
889
  return () => {
@@ -791,12 +930,15 @@ export function createConversationStore(deps) {
791
930
  if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
792
931
  if (event.kind === 'list') {
793
932
  listing.clear();
794
- for (const entry of event.entries)
933
+ for (const entry of event.entries) {
795
934
  listing.set(entry.conversation, { ...entry });
935
+ creations.delete(entry.conversation);
936
+ }
796
937
  listKnown = true;
797
938
  }
798
939
  else if (event.kind === 'listed') {
799
940
  listing.set(event.entry.conversation, { ...event.entry });
941
+ creations.delete(event.entry.conversation);
800
942
  }
801
943
  else {
802
944
  listing.delete(event.conversation);
@@ -810,12 +952,11 @@ export function createConversationStore(deps) {
810
952
  heard(event);
811
953
  return false;
812
954
  },
813
- messages() {
814
- return [...messages].flatMap(([conversation, held]) => held.map(({ identity, text }) => ({ conversation, identity, text })));
955
+ searchable() {
956
+ return [...(listKnown ? listing.keys() : stored)].sort();
815
957
  },
816
- message(conversation, identity) {
817
- const held = messages.get(conversation)?.find((message) => message.identity === identity);
818
- return held === undefined ? null : { conversation, identity, text: held.text };
958
+ acquired(conversation) {
959
+ return reads.has(conversation) && !partials.has(conversation);
819
960
  },
820
961
  close() {
821
962
  views.clear();