@agentshouse/mdruntime 0.1.0-cd3ee807e46ae → 0.1.0-cd50a4607cb7d

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 (106) hide show
  1. package/AGENTS.md +247 -269
  2. package/dist/adapter.d.ts +25 -2
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/attempt.d.ts.map +1 -1
  5. package/dist/attempt.js +14 -1
  6. package/dist/bootstrap.d.ts +2 -2
  7. package/dist/bootstrap.d.ts.map +1 -1
  8. package/dist/bootstrap.js +28 -6
  9. package/dist/change-set.d.ts +2 -0
  10. package/dist/change-set.d.ts.map +1 -1
  11. package/dist/change-set.js +34 -9
  12. package/dist/chat/acts.d.ts +3 -4
  13. package/dist/chat/acts.d.ts.map +1 -1
  14. package/dist/chat/acts.js +10 -10
  15. package/dist/chat/list.d.ts +6 -0
  16. package/dist/chat/list.d.ts.map +1 -1
  17. package/dist/chat/list.js +6 -3
  18. package/dist/chat/markdown.d.ts +2 -1
  19. package/dist/chat/markdown.d.ts.map +1 -1
  20. package/dist/chat/markdown.js +7 -6
  21. package/dist/chat/message.d.ts.map +1 -1
  22. package/dist/chat/message.js +2 -2
  23. package/dist/chat/provider.d.ts.map +1 -1
  24. package/dist/chat/provider.js +25 -50
  25. package/dist/chat/ready.d.ts.map +1 -1
  26. package/dist/chat/ready.js +2 -2
  27. package/dist/chat/style.js +3 -3
  28. package/dist/client.d.ts +7 -7
  29. package/dist/client.d.ts.map +1 -1
  30. package/dist/client.js +1 -1
  31. package/dist/collection.d.ts +17 -3
  32. package/dist/collection.d.ts.map +1 -1
  33. package/dist/collection.js +230 -19
  34. package/dist/connection.d.ts +8 -5
  35. package/dist/connection.d.ts.map +1 -1
  36. package/dist/connection.js +866 -301
  37. package/dist/conversation.d.ts +12 -7
  38. package/dist/conversation.d.ts.map +1 -1
  39. package/dist/conversation.js +258 -130
  40. package/dist/entry.d.ts +17 -0
  41. package/dist/entry.d.ts.map +1 -0
  42. package/dist/entry.js +156 -0
  43. package/dist/fence.d.ts +6 -0
  44. package/dist/fence.d.ts.map +1 -0
  45. package/dist/fence.js +10 -0
  46. package/dist/host.d.ts +4 -2
  47. package/dist/host.d.ts.map +1 -1
  48. package/dist/host.js +9 -5
  49. package/dist/identity.d.ts +2 -0
  50. package/dist/identity.d.ts.map +1 -1
  51. package/dist/identity.js +6 -0
  52. package/dist/index.d.ts +5 -4
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +1 -0
  55. package/dist/memory.d.ts +1 -0
  56. package/dist/memory.d.ts.map +1 -1
  57. package/dist/memory.js +10 -1
  58. package/dist/model.d.ts +1 -0
  59. package/dist/model.d.ts.map +1 -1
  60. package/dist/model.js +9 -0
  61. package/dist/observation.d.ts +5 -2
  62. package/dist/observation.d.ts.map +1 -1
  63. package/dist/observation.js +22 -11
  64. package/dist/outcome.d.ts +4 -0
  65. package/dist/outcome.d.ts.map +1 -1
  66. package/dist/outcome.js +1 -0
  67. package/dist/overlay.d.ts +1 -0
  68. package/dist/overlay.d.ts.map +1 -1
  69. package/dist/overlay.js +33 -54
  70. package/dist/query.d.ts +28 -3
  71. package/dist/query.d.ts.map +1 -1
  72. package/dist/reach.js +3 -3
  73. package/dist/react.d.ts +3 -3
  74. package/dist/react.d.ts.map +1 -1
  75. package/dist/read-side.d.ts +13 -6
  76. package/dist/read-side.d.ts.map +1 -1
  77. package/dist/read-side.js +282 -83
  78. package/dist/recovery.d.ts +25 -0
  79. package/dist/recovery.d.ts.map +1 -0
  80. package/dist/recovery.js +197 -0
  81. package/dist/refresh.d.ts.map +1 -1
  82. package/dist/refresh.js +11 -10
  83. package/dist/replica.d.ts +1 -1
  84. package/dist/replica.d.ts.map +1 -1
  85. package/dist/replica.js +47 -19
  86. package/dist/rows.d.ts +4 -13
  87. package/dist/rows.d.ts.map +1 -1
  88. package/dist/rows.js +2 -12
  89. package/dist/save.d.ts +6 -2
  90. package/dist/save.d.ts.map +1 -1
  91. package/dist/save.js +42 -18
  92. package/dist/search.d.ts +28 -6
  93. package/dist/search.d.ts.map +1 -1
  94. package/dist/search.js +109 -44
  95. package/dist/shared.d.ts +11 -0
  96. package/dist/shared.d.ts.map +1 -0
  97. package/dist/shared.js +368 -0
  98. package/dist/status.d.ts +4 -0
  99. package/dist/status.d.ts.map +1 -1
  100. package/dist/store.d.ts +35 -10
  101. package/dist/store.d.ts.map +1 -1
  102. package/dist/store.js +275 -74
  103. package/dist/turn.d.ts +7 -0
  104. package/dist/turn.d.ts.map +1 -0
  105. package/dist/turn.js +45 -0
  106. package/package.json +6 -2
@@ -1,7 +1,8 @@
1
1
  import { conversationChangeSet } from './change-set.js';
2
2
  import { runtimeError } from './failure.js';
3
3
  import { declaredFile } from './file.js';
4
- import { chatDraftRevision } from './identity.js';
4
+ import { changeBatchIdentity, chatDraftRevision } from './identity.js';
5
+ import { OFFLINE } from './outcome.js';
5
6
  const QUIET = {
6
7
  turn: null,
7
8
  questions: [],
@@ -21,6 +22,7 @@ export function unheldView(conversation) {
21
22
  chatDraft: null,
22
23
  };
23
24
  }
25
+ export const SESSION_STARTED = 'Session started';
24
26
  function startedTurn(turn, operation) {
25
27
  if (operation.op === 'begin')
26
28
  return { turn, started: operation.at, parts: [], context: null };
@@ -77,11 +79,15 @@ function ordered(left, right) {
77
79
  return left.identity < right.identity ? -1 : 1;
78
80
  return 0;
79
81
  }
80
- function merged(held, arriving) {
82
+ function windowOf(held, arriving, limit) {
81
83
  const byIdentity = new Map(held.map((message) => [message.identity, message]));
82
84
  for (const message of arriving)
83
85
  byIdentity.set(message.identity, message);
84
- return [...byIdentity.values()].sort(ordered);
86
+ const next = [...byIdentity.values()].sort(ordered);
87
+ return limit === undefined ? next : next.slice(Math.max(0, next.length - limit));
88
+ }
89
+ function newestOf(arriving, from) {
90
+ return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
85
91
  }
86
92
  function acceptedMessage(value) {
87
93
  if (typeof value !== 'object' || value === null)
@@ -158,9 +164,29 @@ function limitOf(options) {
158
164
  }
159
165
  return limit;
160
166
  }
167
+ const EXPIRED = { kind: 'rejected', code: 'expired', fields: {}, retryable: false };
168
+ function unwritten(draft) {
169
+ return draft.text === '' && draft.files.length === 0;
170
+ }
171
+ function pending(draft) {
172
+ return draft.submitted === undefined ? {} : { submitted: draft.submitted };
173
+ }
174
+ function withSubmitted(draft, submitted) {
175
+ const { submitted: _held, ...unsent } = draft;
176
+ return submitted.length === 0 ? unsent : { ...unsent, submitted };
177
+ }
178
+ function returned(draft, submission, others) {
179
+ return withSubmitted({
180
+ conversation: draft.conversation,
181
+ text: [submission.text, draft.text].filter((one) => one !== '').join('\n'),
182
+ files: [...submission.files, ...draft.files],
183
+ revision: chatDraftRevision(),
184
+ }, others);
185
+ }
161
186
  export function createConversationStore(deps) {
162
- const messages = new Map();
187
+ const stored = new Set();
163
188
  const reads = new Map();
189
+ const partials = new Set();
164
190
  const drafts = new Map();
165
191
  const sending = new Map();
166
192
  const behind = new Set();
@@ -179,49 +205,51 @@ export function createConversationStore(deps) {
179
205
  function liveOf(conversation) {
180
206
  return lives.get(conversation) ?? QUIET;
181
207
  }
182
- function shownMessages(conversation, limit) {
183
- const held = messages.get(conversation) ?? [];
184
- const shown = limit === undefined ? held : held.slice(Math.max(0, held.length - limit));
185
- return shown.map(({ identity, turn, text, message }) => ({
186
- identity,
187
- turn,
188
- text,
189
- message: structuredClone(message),
190
- }));
191
- }
192
- function viewOf(conversation, limit, shown = shownMessages(conversation, limit)) {
208
+ function viewOf(conversation, held) {
193
209
  const draft = drafts.get(conversation);
194
210
  const facts = liveOf(conversation);
195
211
  return {
196
212
  conversation,
197
- messages: shown,
213
+ messages: held.map(({ identity, turn, text, message }) => ({
214
+ identity,
215
+ turn,
216
+ text,
217
+ message,
218
+ })),
198
219
  turn: structuredClone(facts.turn),
199
220
  questions: structuredClone(facts.questions),
200
221
  process: facts.process === null ? null : { ...facts.process },
201
222
  commands: structuredClone(facts.commands),
202
223
  options: structuredClone(facts.options),
203
- chatDraft: draft === undefined || draft.turn !== null || sending.get(conversation)?.draft === draft
224
+ chatDraft: draft === undefined || unwritten(draft)
204
225
  ? null
205
226
  : { text: draft.text, files: [...draft.files], revision: draft.revision },
206
227
  };
207
228
  }
208
- function emit(conversation, messagesChanged = true) {
229
+ function emit(conversation) {
209
230
  if (deps.closed())
210
231
  return;
211
232
  for (const view of views.values()) {
212
- if (view.conversation !== conversation)
233
+ if (view.conversation !== conversation || view.window === null)
213
234
  continue;
214
- if (messagesChanged)
215
- view.messages = null;
216
235
  const ready = [...view.watchers].filter((watcher) => watcher.ready);
217
236
  if (ready.length === 0)
218
237
  continue;
219
- view.messages ??= shownMessages(conversation, view.limit);
220
- const value = viewOf(conversation, view.limit, view.messages);
238
+ const value = viewOf(conversation, view.window);
221
239
  for (const watcher of ready)
222
240
  watcher.notify(value);
223
241
  }
224
242
  }
243
+ function slide(conversation, arriving) {
244
+ for (const view of views.values()) {
245
+ if (view.conversation !== conversation || view.window === null)
246
+ continue;
247
+ view.window = arriving === null ? [] : windowOf(view.window, arriving, view.limit);
248
+ }
249
+ }
250
+ function changesOf(conversation, arriving) {
251
+ return arriving.map(({ identity, text }) => ({ conversation, identity, text }));
252
+ }
225
253
  function stateOf(conversation) {
226
254
  const facts = liveOf(conversation);
227
255
  if (facts.questions.length > 0)
@@ -278,7 +306,10 @@ export function createConversationStore(deps) {
278
306
  const declaration = deps.agents();
279
307
  if (declaration === undefined)
280
308
  return;
281
- const { documents } = await deps.documents(declaration.locator);
309
+ const held = await deps.documents(declaration);
310
+ if ('pending' in held)
311
+ return;
312
+ const { documents } = held;
282
313
  const names = new Map();
283
314
  const retired = new Set();
284
315
  for (const document of documents) {
@@ -310,7 +341,7 @@ export function createConversationStore(deps) {
310
341
  }
311
342
  function live(conversation, change) {
312
343
  setLive(conversation, { ...liveOf(conversation), ...change });
313
- emit(conversation, false);
344
+ emit(conversation);
314
345
  }
315
346
  function relayed(conversation, turn, operation) {
316
347
  const held = liveOf(conversation).turn;
@@ -338,21 +369,25 @@ export function createConversationStore(deps) {
338
369
  }
339
370
  async function remove(leaving) {
340
371
  await deps.durably(async (local) => {
341
- await local.dropConversations(leaving);
342
372
  const removed = [];
343
- const changed = [];
344
373
  for (const conversation of leaving) {
345
- for (const message of messages.get(conversation) ?? []) {
346
- removed.push({ conversation, identity: message.identity });
374
+ for (const identity of await local.messageIdentities(conversation)) {
375
+ removed.push({ conversation, identity, text: null });
347
376
  }
348
- if (messages.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
377
+ }
378
+ await local.dropConversations(leaving);
379
+ const changed = [];
380
+ for (const conversation of leaving) {
381
+ if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
349
382
  changed.push(conversation);
350
383
  }
351
- messages.delete(conversation);
384
+ stored.delete(conversation);
352
385
  reads.delete(conversation);
386
+ partials.delete(conversation);
353
387
  drafts.delete(conversation);
354
388
  behind.delete(conversation);
355
389
  setLive(conversation, null);
390
+ slide(conversation, null);
356
391
  }
357
392
  if (changed.length > 0)
358
393
  deps.publish(conversationChangeSet(removed, changed));
@@ -370,30 +405,25 @@ export function createConversationStore(deps) {
370
405
  const advances = read !== undefined &&
371
406
  !behind.has(conversation) &&
372
407
  (read === null || message.position > read);
373
- const sealed = drafts.get(conversation)?.turn === message.turn;
408
+ await sealed(local, conversation, new Set([message.turn]));
374
409
  await local.keepMessages({
375
410
  conversation,
376
411
  messages: [message],
377
- ...(advances ? { read: message.position } : {}),
378
- sealed,
412
+ ...(advances ? { read: { at: message.position, partial: false } } : {}),
379
413
  });
380
- messages.set(conversation, merged(messages.get(conversation) ?? [], [message]));
414
+ stored.add(conversation);
381
415
  if (advances)
382
416
  reads.set(conversation, message.position);
383
- if (sealed)
384
- drafts.delete(conversation);
385
417
  sealTurns(conversation, new Set([message.turn]));
386
- deps.publish(conversationChangeSet([{ conversation, identity: message.identity }], [conversation]));
418
+ slide(conversation, [message]);
419
+ deps.publish(conversationChangeSet(changesOf(conversation, [message]), [conversation]));
387
420
  });
388
421
  }
389
422
  async function readHistories(conversations) {
390
423
  const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
391
- const starts = new Map(conversations.map((one) => [one, reads.get(one)]));
392
- const arriving = new Map();
393
- const complete = new Set();
394
424
  let asking = conversations.map((conversation) => ({
395
425
  conversation,
396
- after: starts.get(conversation) ?? undefined,
426
+ after: reads.get(conversation) ?? undefined,
397
427
  }));
398
428
  while (asking.length > 0) {
399
429
  const result = await deps.call('conversation.history', {
@@ -405,46 +435,41 @@ export function createConversationStore(deps) {
405
435
  const pages = historyPages(result);
406
436
  if (pages === null)
407
437
  return result;
408
- const asked = new Set(asking.map(({ conversation }) => conversation));
438
+ const asked = new Map(asking.map(({ conversation, after }) => [conversation, after]));
409
439
  const following = [];
410
440
  for (const page of pages) {
411
441
  if (!asked.has(page.conversation))
412
442
  continue;
413
- arriving.set(page.conversation, [
414
- ...(arriving.get(page.conversation) ?? []),
415
- ...page.messages,
416
- ]);
417
- if (page.next === undefined)
418
- complete.add(page.conversation);
419
- else
443
+ await keepPage(page.conversation, page.messages, asked.get(page.conversation) ?? null, page.next, generations.get(page.conversation) ?? 0);
444
+ if (page.next !== undefined) {
420
445
  following.push({ conversation: page.conversation, after: page.next });
446
+ }
421
447
  }
422
448
  asking = following;
423
449
  }
424
- for (const [conversation, held] of arriving) {
425
- if (!complete.has(conversation))
426
- continue;
427
- await keepHistory(conversation, held, starts.get(conversation), generations.get(conversation) ?? 0);
428
- }
429
450
  return null;
430
451
  }
431
- function keepHistory(conversation, arriving, from, generation) {
452
+ function keepPage(conversation, arriving, from, next, generation) {
432
453
  return deps.durably(async (local) => {
433
454
  if (!replicated.has(conversation))
434
455
  return;
435
- const newest = arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from ?? null);
436
- const turn = drafts.get(conversation)?.turn;
437
- const sealed = arriving.some((message) => message.turn === turn);
438
- await local.keepMessages({ conversation, messages: arriving, read: newest, sealed });
439
- messages.set(conversation, merged(messages.get(conversation) ?? [], arriving));
440
- reads.set(conversation, newest);
441
- if (sealed)
442
- drafts.delete(conversation);
456
+ const whole = next === undefined;
457
+ const cursor = next ?? newestOf(arriving, from);
458
+ const partial = !whole && (partials.has(conversation) || !reads.has(conversation));
459
+ const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
460
+ await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
461
+ stored.add(conversation);
462
+ reads.set(conversation, cursor);
463
+ if (partial)
464
+ partials.add(conversation);
465
+ else
466
+ partials.delete(conversation);
443
467
  sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
444
- if ((gaps.get(conversation) ?? 0) === generation)
468
+ if (whole && (gaps.get(conversation) ?? 0) === generation)
445
469
  behind.delete(conversation);
446
- if (arriving.length > 0 || sealed) {
447
- deps.publish(conversationChangeSet(arriving.map((message) => ({ conversation, identity: message.identity })), [conversation]));
470
+ slide(conversation, arriving);
471
+ if (arriving.length > 0 || sealing || whole) {
472
+ deps.publish(conversationChangeSet(changesOf(conversation, arriving), [conversation]));
448
473
  }
449
474
  });
450
475
  }
@@ -457,7 +482,7 @@ export function createConversationStore(deps) {
457
482
  if (held === undefined)
458
483
  return;
459
484
  setLive(conversation, { ...QUIET, turn: held.turn });
460
- emit(conversation, false);
485
+ emit(conversation);
461
486
  return;
462
487
  }
463
488
  if (event.kind === 'turn') {
@@ -490,22 +515,103 @@ export function createConversationStore(deps) {
490
515
  if (sealTurns(conversation, new Set([arriving.turn])))
491
516
  emit(conversation);
492
517
  }
518
+ async function kept(local, conversation, following) {
519
+ if (following === null || (unwritten(following) && following.submitted === undefined)) {
520
+ await local.dropChatDraft(conversation);
521
+ drafts.delete(conversation);
522
+ }
523
+ else {
524
+ await local.keepChatDraft(following);
525
+ drafts.set(conversation, following);
526
+ }
527
+ }
528
+ async function sealed(local, conversation, turns) {
529
+ const held = drafts.get(conversation);
530
+ const submitted = held?.submitted ?? [];
531
+ const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
532
+ if (held === undefined || left.length === submitted.length)
533
+ return false;
534
+ await kept(local, conversation, withSubmitted(held, left));
535
+ return true;
536
+ }
493
537
  function withDraft(conversation, next) {
494
538
  return deps.durably(async (local) => {
495
- const following = next(drafts.get(conversation));
539
+ const following = await next(drafts.get(conversation), local);
496
540
  if (following === undefined)
497
541
  return;
498
- if (following === null) {
499
- await local.dropChatDraft(conversation);
500
- drafts.delete(conversation);
501
- }
502
- else {
503
- await local.keepChatDraft(following);
504
- drafts.set(conversation, following);
505
- }
542
+ await kept(local, conversation, following);
506
543
  deps.publish(conversationChangeSet([], [conversation]));
507
544
  });
508
545
  }
546
+ async function submitted(conversation, operation, draft) {
547
+ let submission;
548
+ let earlier = false;
549
+ await withDraft(conversation, (current) => {
550
+ submission = current?.submitted?.find((one) => one.operation === operation);
551
+ earlier = submission !== undefined;
552
+ if (submission !== undefined)
553
+ return undefined;
554
+ const said = draft ?? current;
555
+ submission = {
556
+ operation,
557
+ text: said?.text ?? '',
558
+ files: [...(said?.files ?? [])],
559
+ turn: null,
560
+ };
561
+ return {
562
+ conversation,
563
+ ...(draft === undefined || current === undefined
564
+ ? { text: '', files: [], revision: chatDraftRevision() }
565
+ : { text: current.text, files: current.files, revision: current.revision }),
566
+ submitted: [...(current?.submitted ?? []), submission],
567
+ };
568
+ });
569
+ return { submission: submission, earlier };
570
+ }
571
+ function forgotten(conversation, submission) {
572
+ return withDraft(conversation, (current) => {
573
+ const held = current?.submitted ?? [];
574
+ if (current === undefined || !held.includes(submission))
575
+ return undefined;
576
+ return withSubmitted(current, held.filter((one) => one !== submission));
577
+ });
578
+ }
579
+ async function dispatched(conversation, submission) {
580
+ const dispatch = {};
581
+ sending.set(submission.operation, dispatch);
582
+ try {
583
+ const files = await Promise.all(submission.files.map(async (file) => ({ ...(await declaredFile(file)), blob: file.blob })));
584
+ const result = await invoke('conversation.send', conversation, {
585
+ text: submission.text,
586
+ files,
587
+ operation_id: submission.operation,
588
+ });
589
+ if (result.kind === 'revoked-authorization')
590
+ return result;
591
+ if (result.kind !== 'action' && result.retryable) {
592
+ deps.unanswered();
593
+ return result;
594
+ }
595
+ const turn = turnOf(result);
596
+ await withDraft(conversation, async (current, local) => {
597
+ const held = current?.submitted?.find((one) => one.operation === submission.operation);
598
+ if (current === undefined || held === undefined || held.turn !== null)
599
+ return undefined;
600
+ const others = current.submitted.filter((one) => one !== held);
601
+ if (result.kind !== 'action')
602
+ return returned(current, held, others);
603
+ const arrived = turn !== null && (await local.turnArrived(conversation, turn));
604
+ return withSubmitted(current, turn === null || arrived
605
+ ? others
606
+ : current.submitted.map((one) => (one === held ? { ...held, turn } : one)));
607
+ });
608
+ return result;
609
+ }
610
+ finally {
611
+ if (sending.get(submission.operation) === dispatch)
612
+ sending.delete(submission.operation);
613
+ }
614
+ }
509
615
  function invoke(name, conversation, extra = {}) {
510
616
  return deps.call(name, { conversation, ...extra });
511
617
  }
@@ -516,46 +622,25 @@ export function createConversationStore(deps) {
516
622
  },
517
623
  async read(conversation, options = {}) {
518
624
  const limit = limitOf(options);
519
- return deps.durably(() => Promise.resolve(viewOf(conversation, limit)));
625
+ return deps.durably(async (local) => structuredClone(viewOf(conversation, await local.messageWindow(conversation, limit))));
520
626
  },
521
627
  create(input) {
522
628
  return deps.call('conversation.create', input);
523
629
  },
524
- async send(conversation) {
525
- const draft = drafts.get(conversation);
526
- const pending = draft === undefined ? undefined : { draft };
527
- if (pending !== undefined) {
528
- sending.set(conversation, pending);
529
- deps.publish(conversationChangeSet([], [conversation]));
530
- }
531
- try {
532
- const files = await Promise.all((draft?.files ?? []).map(async (file) => ({
533
- ...(await declaredFile(file)),
534
- blob: file.blob,
535
- })));
536
- const result = await invoke('conversation.send', conversation, {
537
- text: draft?.text ?? '',
538
- files,
539
- });
540
- const turn = turnOf(result);
541
- if (turn === null || draft === undefined)
542
- return result;
543
- await withDraft(conversation, (current) => {
544
- if (current !== draft)
545
- return undefined;
546
- const sealed = (messages.get(conversation) ?? []).some((message) => message.turn === turn);
547
- return sealed ? null : { ...draft, turn };
548
- });
549
- return result;
630
+ async send(conversation, operation = changeBatchIdentity(), draft) {
631
+ if (deps.online()) {
632
+ const { submission, earlier } = await submitted(conversation, operation, draft);
633
+ if (!earlier || !deps.expired(operation))
634
+ return dispatched(conversation, submission);
635
+ await forgotten(conversation, submission);
636
+ return EXPIRED;
550
637
  }
551
- finally {
552
- if (pending !== undefined && sending.get(conversation) === pending) {
553
- sending.delete(conversation);
554
- if (drafts.get(conversation) === draft) {
555
- deps.publish(conversationChangeSet([], [conversation]));
556
- }
557
- }
638
+ if (draft !== undefined) {
639
+ await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
640
+ ? undefined
641
+ : returned(current ?? { conversation, text: '', files: [], revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
558
642
  }
643
+ return OFFLINE;
559
644
  },
560
645
  answer(conversation, answer) {
561
646
  return invoke('conversation.answer', conversation, { answer });
@@ -570,7 +655,12 @@ export function createConversationStore(deps) {
570
655
  const result = await invoke('conversation.cancel', conversation, { turn });
571
656
  if (result.kind !== 'action')
572
657
  return result;
573
- await withDraft(conversation, (current) => current === undefined || current.turn !== turn ? undefined : { ...current, turn: null });
658
+ await withDraft(conversation, (current) => {
659
+ const held = current?.submitted?.find((one) => one.turn === turn);
660
+ if (current === undefined || held === undefined)
661
+ return undefined;
662
+ return returned(current, held, current.submitted.filter((one) => one !== held));
663
+ });
574
664
  return result;
575
665
  },
576
666
  delete(conversation) {
@@ -579,33 +669,42 @@ export function createConversationStore(deps) {
579
669
  };
580
670
  const chatDraft = {
581
671
  write(conversation, draft, revision = chatDraftRevision()) {
582
- return withDraft(conversation, () => ({
672
+ return withDraft(conversation, (current) => ({
583
673
  conversation,
584
674
  text: draft.text,
585
675
  files: [...draft.files],
586
- turn: null,
587
676
  revision,
677
+ ...(current?.submitted === undefined ? {} : { submitted: current.submitted }),
588
678
  }));
589
679
  },
590
680
  discard(conversation) {
591
- return withDraft(conversation, (current) => (current === undefined ? undefined : null));
681
+ return withDraft(conversation, (current) => current === undefined
682
+ ? undefined
683
+ : {
684
+ conversation,
685
+ text: '',
686
+ files: [],
687
+ revision: chatDraftRevision(),
688
+ ...pending(current),
689
+ });
592
690
  },
593
691
  };
594
692
  return {
595
693
  surface,
596
694
  chatDraft,
597
- hydrate(held, stored) {
598
- messages.clear();
695
+ hydrate(held, kept) {
696
+ stored.clear();
599
697
  reads.clear();
600
- for (const view of views.values())
601
- view.messages = null;
698
+ partials.clear();
602
699
  for (const entry of held) {
603
- messages.set(entry.conversation, [...entry.messages].sort(ordered));
700
+ stored.add(entry.conversation);
604
701
  if (entry.read !== undefined)
605
702
  reads.set(entry.conversation, entry.read);
703
+ if (entry.partial)
704
+ partials.add(entry.conversation);
606
705
  }
607
706
  drafts.clear();
608
- for (const record of stored)
707
+ for (const record of kept)
609
708
  drafts.set(record.conversation, record);
610
709
  },
611
710
  async reconcile() {
@@ -613,7 +712,7 @@ export function createConversationStore(deps) {
613
712
  return;
614
713
  const named = new Set(listing.keys());
615
714
  const leaving = [
616
- ...new Set([...replicated, ...messages.keys(), ...reads.keys(), ...drafts.keys()]),
715
+ ...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
617
716
  ].filter((conversation) => !named.has(conversation));
618
717
  for (const conversation of named) {
619
718
  if (!replicated.has(conversation))
@@ -634,6 +733,31 @@ export function createConversationStore(deps) {
634
733
  if (behind.size > 0)
635
734
  progress.complete = false;
636
735
  },
736
+ async resend(progress) {
737
+ for (const [conversation, held] of [...drafts]) {
738
+ for (const submission of held.submitted ?? []) {
739
+ if (submission.turn !== null || sending.has(submission.operation))
740
+ continue;
741
+ if (deps.closed())
742
+ return;
743
+ if (deps.expired(submission.operation)) {
744
+ await forgotten(conversation, submission);
745
+ continue;
746
+ }
747
+ const result = await dispatched(conversation, submission);
748
+ if (result.kind === 'revoked-authorization')
749
+ return;
750
+ if (result.kind === 'transport-uncertainty') {
751
+ progress.reachable = false;
752
+ return;
753
+ }
754
+ if (result.kind !== 'action' && result.retryable) {
755
+ progress.complete = false;
756
+ return;
757
+ }
758
+ }
759
+ }
760
+ },
637
761
  current() {
638
762
  return behind.size === 0;
639
763
  },
@@ -641,6 +765,9 @@ export function createConversationStore(deps) {
641
765
  for (const conversation of replicated)
642
766
  gap(conversation);
643
767
  },
768
+ reopened() {
769
+ sending.clear();
770
+ },
644
771
  watch(conversation, options, deliver) {
645
772
  const limit = limitOf(options);
646
773
  const key = JSON.stringify([conversation, limit ?? null]);
@@ -648,18 +775,20 @@ export function createConversationStore(deps) {
648
775
  conversation,
649
776
  limit,
650
777
  watchers: new Set(),
651
- messages: null,
778
+ window: null,
652
779
  };
653
780
  views.set(key, view);
654
781
  const watcher = { notify: deliver, ready: false };
655
782
  view.watchers.add(watcher);
656
783
  void deps
657
- .durably(() => Promise.resolve())
784
+ .durably(async (local) => {
785
+ view.window ??= await local.messageWindow(conversation, limit);
786
+ })
658
787
  .then(() => {
659
788
  if (deps.closed() || !view.watchers.has(watcher))
660
789
  return;
661
790
  watcher.ready = true;
662
- watcher.notify(viewOf(conversation, limit));
791
+ watcher.notify(viewOf(conversation, view.window ?? []));
663
792
  })
664
793
  .catch(() => { });
665
794
  return () => {
@@ -725,12 +854,11 @@ export function createConversationStore(deps) {
725
854
  heard(event);
726
855
  return false;
727
856
  },
728
- messages() {
729
- return [...messages].flatMap(([conversation, held]) => held.map(({ identity, text }) => ({ conversation, identity, text })));
857
+ searchable() {
858
+ return [...(listKnown ? listing.keys() : stored)].sort();
730
859
  },
731
- message(conversation, identity) {
732
- const held = messages.get(conversation)?.find((message) => message.identity === identity);
733
- return held === undefined ? null : { conversation, identity, text: held.text };
860
+ acquired(conversation) {
861
+ return reads.has(conversation) && !partials.has(conversation);
734
862
  },
735
863
  close() {
736
864
  views.clear();
@@ -0,0 +1,17 @@
1
+ import { type QueryRows } from './query.js';
2
+ export { QueryError } from './query.js';
3
+ export type EntryRoom = {
4
+ locator: string;
5
+ schema: string | null;
6
+ documents: Readonly<Record<string, string>>;
7
+ };
8
+ export type EntryArguments = Readonly<Record<string, string>>;
9
+ export type EntryParameterKind = 'string' | 'integer' | 'number' | 'boolean';
10
+ export declare function boundQuery(template: unknown, args: EntryArguments): unknown;
11
+ export declare function entryParameters(template: unknown): readonly string[];
12
+ export declare function entryRefusal(template: unknown): string | null;
13
+ export type EntryScope = {
14
+ results(queries: Readonly<Record<string, unknown>>): Promise<Record<string, QueryRows>>;
15
+ };
16
+ export declare function entryScope(rooms: readonly EntryRoom[]): EntryScope;
17
+ //# sourceMappingURL=entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAGA,OAAO,EAML,KAAK,SAAS,EAEf,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC7C,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAE9D,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AA0D7E,wBAAgB,UAAU,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAE3E;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,MAAM,EAAE,CAMpE;AAkCD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAQ7D;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;CACzF,CAAC;AAEF,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,UAAU,CAqDlE"}