@agentshouse/mdruntime 0.1.0-c014edb0e9d9b → 0.1.0-c01d4615bfe46

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 (149) hide show
  1. package/README.md +16 -15
  2. package/dist/acceptance.d.ts.map +1 -1
  3. package/dist/acceptance.js +1 -20
  4. package/dist/act-lanes.d.ts +11 -14
  5. package/dist/act-lanes.d.ts.map +1 -1
  6. package/dist/act-lanes.js +53 -5
  7. package/dist/actions.d.ts +1 -0
  8. package/dist/actions.d.ts.map +1 -1
  9. package/dist/actions.js +88 -47
  10. package/dist/adapter.d.ts +20 -12
  11. package/dist/adapter.d.ts.map +1 -1
  12. package/dist/attached-model.d.ts +0 -1
  13. package/dist/attached-model.d.ts.map +1 -1
  14. package/dist/attached-model.js +0 -5
  15. package/dist/chat-types.d.ts +0 -2
  16. package/dist/chat-types.d.ts.map +1 -1
  17. package/dist/chat-types.js +0 -2
  18. package/dist/client.d.ts +6 -9
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +6 -5
  21. package/dist/compile.d.ts +2 -0
  22. package/dist/compile.d.ts.map +1 -1
  23. package/dist/compile.js +17 -4
  24. package/dist/connection.d.ts.map +1 -1
  25. package/dist/connection.js +5 -10
  26. package/dist/conversation-live.d.ts +13 -4
  27. package/dist/conversation-live.d.ts.map +1 -1
  28. package/dist/conversation-live.js +15 -6
  29. package/dist/conversation.d.ts +11 -3
  30. package/dist/conversation.d.ts.map +1 -1
  31. package/dist/conversation.js +201 -154
  32. package/dist/discovery.d.ts.map +1 -1
  33. package/dist/discovery.js +1 -4
  34. package/dist/durability.d.ts +1 -0
  35. package/dist/durability.d.ts.map +1 -1
  36. package/dist/durability.js +4 -0
  37. package/dist/entry.js +1 -1
  38. package/dist/expectation.d.ts +25 -14
  39. package/dist/expectation.d.ts.map +1 -1
  40. package/dist/expectation.js +51 -20
  41. package/dist/file.d.ts.map +1 -1
  42. package/dist/file.js +2 -1
  43. package/dist/host.d.ts +1 -0
  44. package/dist/host.d.ts.map +1 -1
  45. package/dist/host.js +8 -3
  46. package/dist/index.d.ts +5 -4
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +5 -4
  49. package/dist/install.d.ts +1 -1
  50. package/dist/install.d.ts.map +1 -1
  51. package/dist/install.js +4 -13
  52. package/dist/lifecycle.d.ts.map +1 -1
  53. package/dist/lifecycle.js +3 -5
  54. package/dist/locks.d.ts.map +1 -1
  55. package/dist/locks.js +0 -1
  56. package/dist/lost.d.ts.map +1 -1
  57. package/dist/lost.js +1 -2
  58. package/dist/media.d.ts.map +1 -1
  59. package/dist/media.js +6 -9
  60. package/dist/memory.d.ts +3 -1
  61. package/dist/memory.d.ts.map +1 -1
  62. package/dist/memory.js +20 -7
  63. package/dist/messages.d.ts +9 -2
  64. package/dist/messages.d.ts.map +1 -1
  65. package/dist/messages.js +15 -7
  66. package/dist/models.d.ts.map +1 -1
  67. package/dist/models.js +2 -5
  68. package/dist/observation.d.ts +3 -2
  69. package/dist/observation.d.ts.map +1 -1
  70. package/dist/observation.js +11 -5
  71. package/dist/opening.d.ts.map +1 -1
  72. package/dist/opening.js +3 -9
  73. package/dist/outcome.d.ts +4 -0
  74. package/dist/outcome.d.ts.map +1 -1
  75. package/dist/outcome.js +8 -2
  76. package/dist/overlay.d.ts +3 -0
  77. package/dist/overlay.d.ts.map +1 -1
  78. package/dist/overlay.js +8 -4
  79. package/dist/parts.d.ts +2 -9
  80. package/dist/parts.d.ts.map +1 -1
  81. package/dist/parts.js +6 -14
  82. package/dist/preparation.d.ts.map +1 -1
  83. package/dist/preparation.js +6 -12
  84. package/dist/queries.d.ts.map +1 -1
  85. package/dist/queries.js +19 -7
  86. package/dist/questions.d.ts +0 -5
  87. package/dist/questions.d.ts.map +1 -1
  88. package/dist/questions.js +0 -7
  89. package/dist/queue.d.ts +2 -2
  90. package/dist/queue.d.ts.map +1 -1
  91. package/dist/queue.js +25 -30
  92. package/dist/reach.d.ts.map +1 -1
  93. package/dist/reach.js +41 -15
  94. package/dist/react.d.ts +6 -11
  95. package/dist/react.d.ts.map +1 -1
  96. package/dist/react.js +22 -4
  97. package/dist/readiness.d.ts +1 -1
  98. package/dist/readiness.d.ts.map +1 -1
  99. package/dist/recovery.d.ts +3 -1
  100. package/dist/recovery.d.ts.map +1 -1
  101. package/dist/recovery.js +29 -7
  102. package/dist/refusal.d.ts.map +1 -1
  103. package/dist/refusal.js +1 -3
  104. package/dist/room-draft.d.ts +2 -2
  105. package/dist/room-draft.d.ts.map +1 -1
  106. package/dist/room-draft.js +26 -21
  107. package/dist/runtime.d.ts +0 -2
  108. package/dist/runtime.d.ts.map +1 -1
  109. package/dist/save.d.ts +1 -0
  110. package/dist/save.d.ts.map +1 -1
  111. package/dist/save.js +105 -55
  112. package/dist/saves.d.ts.map +1 -1
  113. package/dist/saves.js +3 -6
  114. package/dist/shown.d.ts +2 -1
  115. package/dist/shown.d.ts.map +1 -1
  116. package/dist/shown.js +18 -6
  117. package/dist/socket/lanes.d.ts +10 -3
  118. package/dist/socket/lanes.d.ts.map +1 -1
  119. package/dist/socket/lanes.js +29 -6
  120. package/dist/socket.d.ts +2 -3
  121. package/dist/socket.d.ts.map +1 -1
  122. package/dist/socket.js +2 -9
  123. package/dist/source.d.ts +0 -7
  124. package/dist/source.d.ts.map +1 -1
  125. package/dist/store/records.d.ts +8 -5
  126. package/dist/store/records.d.ts.map +1 -1
  127. package/dist/store/records.js +12 -3
  128. package/dist/store.d.ts +1 -1
  129. package/dist/store.d.ts.map +1 -1
  130. package/dist/stored.d.ts +1 -0
  131. package/dist/stored.d.ts.map +1 -1
  132. package/dist/stored.js +5 -5
  133. package/dist/surface.d.ts +4 -3
  134. package/dist/surface.d.ts.map +1 -1
  135. package/dist/surface.js +11 -12
  136. package/dist/sync.d.ts.map +1 -1
  137. package/dist/sync.js +5 -10
  138. package/dist/tables.d.ts +6 -3
  139. package/dist/tables.d.ts.map +1 -1
  140. package/dist/tables.js +34 -10
  141. package/dist/tallies.d.ts.map +1 -1
  142. package/dist/tallies.js +1 -2
  143. package/dist/wire.d.ts +12 -3
  144. package/dist/wire.d.ts.map +1 -1
  145. package/dist/wire.js +4 -1
  146. package/package.json +3 -3
  147. package/dist/log.d.ts +0 -6
  148. package/dist/log.d.ts.map +0 -1
  149. package/dist/log.js +0 -14
@@ -29,12 +29,12 @@ export function unheldView(conversation) {
29
29
  deliveryWaiting: [],
30
30
  deliveryUnknown: [],
31
31
  chatDraft: null,
32
+ dismissedSuggestion: null,
32
33
  };
33
34
  }
34
35
  export const SESSION_STARTED = 'Session started';
35
36
  const NEW_CHAT = 'new:';
36
37
  const LISTING = 'listing';
37
- const AGENTS = 'agents';
38
38
  export const CATCHING_UP = 'catching-up';
39
39
  export const HISTORY_CONVERSATIONS = 64;
40
40
  export function newChat(agent) {
@@ -198,17 +198,17 @@ function withSubmitted(draft, submitted) {
198
198
  const { submitted: _held, ...unsent } = draft;
199
199
  return submitted.length === 0 ? unsent : { ...unsent, submitted };
200
200
  }
201
- function prepended(draft, said) {
202
- return { text: [said.text, draft.text].filter((one) => one !== '').join('\n') };
201
+ export function joinedDraft(first, second) {
202
+ return [first, second].filter((one) => one !== '').join('\n');
203
203
  }
204
204
  function returned(draft, submission, others) {
205
205
  return withSubmitted({
206
206
  conversation: draft.conversation,
207
- ...prepended(draft, submission),
207
+ text: joinedDraft(submission.text, draft.text),
208
208
  revision: chatDraftRevision(),
209
209
  }, others);
210
210
  }
211
- function viewOf(alive, conversation, held, frame, draft, ended) {
211
+ function viewOf(alive, conversation, held, frame, draft, ended, dismissed) {
212
212
  const facts = alive.liveOf(conversation);
213
213
  return {
214
214
  conversation,
@@ -231,6 +231,7 @@ function viewOf(alive, conversation, held, frame, draft, ended) {
231
231
  deliveryWaiting: [...facts.deliveryWaiting],
232
232
  deliveryUnknown: [...facts.deliveryUnknown],
233
233
  chatDraft: draft === null || unwritten(draft) ? null : { text: draft.text, revision: draft.revision },
234
+ dismissedSuggestion: dismissed,
234
235
  };
235
236
  }
236
237
  function sessionOf(facts, held, ended) {
@@ -250,13 +251,14 @@ function emit(alive, conversation) {
250
251
  if (alive.deps.closed())
251
252
  return;
252
253
  for (const view of alive.views()) {
253
- const { window, draft, ended } = view.held.now();
254
+ const { window, draft, ended, dismissed } = view.held.now();
254
255
  if (view.conversation !== conversation || window === null)
255
256
  continue;
256
257
  const ready = view.ready();
257
258
  if (ready.length === 0)
258
259
  continue;
259
- const value = viewOf(alive, conversation, window, sharedFrame(view, window), draft, ended);
260
+ const frame = sharedFrame(view, window);
261
+ const value = viewOf(alive, conversation, window, frame, draft, ended, dismissed);
260
262
  for (const watcher of ready)
261
263
  watcher.notify(value);
262
264
  }
@@ -302,66 +304,90 @@ function rowOf(alive, conversation) {
302
304
  };
303
305
  }
304
306
  async function entries(alive) {
305
- const list = await heldList(alive);
306
- if (list === null)
307
+ const listing = await heldListing(alive);
308
+ if (listing === null)
309
+ return null;
310
+ const list = listing.conversations;
311
+ const agents = new Map(listing.agents.map((agent) => [agent.id, agent]));
312
+ if (list.some((entry) => !agents.has(entry.agent)))
307
313
  return null;
308
314
  const held = list.length === 0
309
315
  ? []
310
316
  : await alive.deps.durably((local) => local.conversationRows(list.map(({ conversation }) => conversation)));
311
317
  const rows = new Map(held.map((one) => [one.conversation, one]));
312
- return list.map((entry) => ({
313
- conversation: entry.conversation,
314
- agent: { identity: entry.agent, name: entry.agentName, retired: entry.agentRetired },
315
- name: entry.name,
316
- created: entry.created,
317
- finished: entry.finished,
318
- outcome: entry.outcome,
319
- waiting: entry.waiting,
320
- ...rowOf(alive, entry.conversation),
321
- ...heldFacts(rows.get(entry.conversation), entry),
322
- }));
318
+ return list.map((entry) => {
319
+ const agent = agents.get(entry.agent);
320
+ return {
321
+ conversation: entry.conversation,
322
+ agent: { identity: agent.id, name: agent.name, retired: agent.retired_at !== null },
323
+ name: entry.name,
324
+ created: entry.created,
325
+ finished: entry.finished,
326
+ outcome: entry.outcome,
327
+ waiting: entry.waiting,
328
+ subject: entry.subject,
329
+ ...rowOf(alive, entry.conversation),
330
+ ...heldFacts(rows.get(entry.conversation), entry),
331
+ };
332
+ });
323
333
  }
324
- async function heldList(alive) {
325
- const stored = (await (await alive.deps.local()).facts()).conversationList;
326
- if (stored !== null)
327
- return stored;
328
- return !alive.known() && alive.deps.stream() !== undefined && alive.deps.online() ? null : [];
334
+ async function heldListing(alive) {
335
+ const { conversationList, agentList } = await (await alive.deps.local()).facts();
336
+ if (conversationList !== null && agentList !== null)
337
+ return { conversations: conversationList, agents: agentList };
338
+ return !alive.known() && alive.deps.stream() !== undefined && alive.deps.online()
339
+ ? null
340
+ : { conversations: [], agents: [] };
329
341
  }
330
342
  async function endedIn(alive, conversation) {
331
- const list = await heldList(alive);
332
- return list?.find((entry) => entry.conversation === conversation)?.processEnded ?? null;
343
+ const listing = await heldListing(alive);
344
+ return (listing?.conversations.find((entry) => entry.conversation === conversation)?.processEnded ??
345
+ null);
346
+ }
347
+ function upserted(kept, set, key) {
348
+ const held = new Map(kept.map((one) => [key(one), one]));
349
+ for (const one of set)
350
+ held.set(key(one), { ...one });
351
+ return held;
333
352
  }
334
353
  function listChanged(alive, change) {
335
354
  void alive.deps
336
355
  .durably(async (local) => {
337
- const { conversationList, conversationCursor } = await local.facts();
338
- const kept = change.replacing ? [] : (conversationList ?? []);
339
- const held = new Map(kept.map((entry) => [entry.conversation, entry]));
340
- for (const entry of change.set) {
341
- held.set(entry.conversation, { ...entry });
356
+ const { conversationList, agentList, conversationCursor } = await local.facts();
357
+ const held = upserted(change.replacing ? [] : (conversationList ?? []), change.set, (entry) => entry.conversation);
358
+ for (const entry of change.set)
342
359
  alive.created(entry.conversation);
343
- }
344
360
  if (change.gone !== null)
345
361
  held.delete(change.gone);
346
362
  const list = [...held.values()];
363
+ const agents = [
364
+ ...upserted(change.replacing ? [] : (agentList ?? []), change.agents, (agent) => agent.id).values(),
365
+ ];
347
366
  if (JSON.stringify(list) !== JSON.stringify(conversationList)) {
348
367
  await local.fact('conversationList', list);
349
368
  }
369
+ if (JSON.stringify(agents) !== JSON.stringify(agentList)) {
370
+ await local.fact('agentList', agents);
371
+ }
350
372
  if (change.cursor !== undefined) {
351
373
  if (change.cursor !== conversationCursor) {
352
374
  await local.fact('conversationCursor', change.cursor);
353
375
  }
354
376
  alive.deps.stream()?.hold(change.cursor);
355
377
  }
356
- return list;
378
+ return { conversations: list, agents };
379
+ })
380
+ .catch(async () => {
381
+ const { conversationList, agentList } = await (await alive.deps.local()).facts();
382
+ return { conversations: conversationList ?? [], agents: agentList ?? [] };
357
383
  })
358
- .catch(async () => (await (await alive.deps.local()).facts()).conversationList ?? [])
359
- .then((list) => {
384
+ .then((listing) => {
360
385
  if (alive.deps.closed() || change.paged === true)
361
386
  return;
387
+ const list = listing.conversations;
362
388
  endedAnew(alive, list);
363
389
  if (change.cursor !== undefined) {
364
- alive.held.set({ listKnown: true });
390
+ alive.held.set({ listKnown: true, openingListed: true });
365
391
  settleBehind(alive, list);
366
392
  openingEnded(alive);
367
393
  alive.deps.settled();
@@ -369,11 +395,12 @@ function listChanged(alive, change) {
369
395
  emitList(alive);
370
396
  if (!alive.known())
371
397
  return;
372
- retiredDrafts(alive, list);
373
- const { replicated } = alive.held.now();
374
- if (change.cursor === undefined &&
375
- (list.length !== replicated.size ||
376
- list.some(({ conversation }) => !replicated.has(conversation)))) {
398
+ retiredDrafts(alive, listing);
399
+ const { replicated, openingRead } = alive.held.now();
400
+ if (change.cursor === undefined
401
+ ? list.length !== replicated.size ||
402
+ list.some(({ conversation }) => !replicated.has(conversation))
403
+ : openingRead) {
377
404
  alive.deps.again();
378
405
  }
379
406
  }, uncaught);
@@ -389,23 +416,23 @@ function endedAnew(alive, list) {
389
416
  emit(alive, view.conversation);
390
417
  }
391
418
  }
392
- function shownList(alive, work) {
393
- const reading = heldList(alive);
419
+ function shownListing(alive, work) {
420
+ const reading = heldListing(alive);
394
421
  alive.inOrder(async () => {
395
- const list = await reading.catch((error) => {
422
+ const listing = await reading.catch((error) => {
396
423
  uncaught(error);
397
424
  return null;
398
425
  });
399
- if (list === null || alive.deps.closed())
426
+ if (listing === null || alive.deps.closed())
400
427
  return;
401
- work(list);
428
+ work(listing);
402
429
  });
403
430
  }
404
431
  async function told(alive) {
405
432
  try {
406
433
  while (alive.held.now().listOwed && !alive.deps.closed()) {
407
434
  alive.held.set({ listOwed: false });
408
- const value = await untilAnswered(retrying(alive.deps.retries, 'conversations', LISTING, () => entries(alive)), alive.closing);
435
+ const value = await untilAnswered(retrying(alive.deps.retries, 'conversations', LISTING, () => entries(alive)), alive.closing, () => true, alive.deps.recovery);
409
436
  if (alive.deps.closed())
410
437
  return;
411
438
  if (value === null)
@@ -443,66 +470,46 @@ export function conversationsLearned(alive) {
443
470
  return;
444
471
  if (alive.listeners.waiting())
445
472
  tellList(alive);
446
- if (!alive.agentListeners.waiting())
473
+ if (!alive.agentListeners.waiting() && !alive.listedListeners.waiting())
447
474
  return;
448
- shownList(alive, (list) => alive.agentListeners.welcome(agents(alive, list)));
475
+ shownListing(alive, (listing) => {
476
+ alive.agentListeners.welcome(agentsOf(listing));
477
+ alive.listedListeners.welcome(structuredClone(listing.agents));
478
+ });
449
479
  }
450
- function agents(alive, list) {
451
- const read = new Map((alive.held.now().listedAgents ?? []).map((one) => [one.agent, one]));
452
- const named = new Map(read);
453
- for (const entry of list) {
454
- named.set(entry.agent, { name: entry.agentName, retired: entry.agentRetired });
455
- }
456
- return [...named]
457
- .filter(([, agent]) => !agent.retired)
458
- .map(([identity, agent]) => {
459
- const held = read.get(identity);
460
- return held === undefined
461
- ? { identity, name: agent.name }
462
- : {
463
- identity,
464
- name: agent.name,
465
- cli: held.cli,
466
- environment: structuredClone(held.environment),
467
- };
468
- })
480
+ function agentsOf(listing) {
481
+ return listing.agents
482
+ .filter((agent) => agent.retired_at === null)
483
+ .map((agent) => ({
484
+ identity: agent.id,
485
+ name: agent.name,
486
+ cli: agent.cli,
487
+ environment: {
488
+ identity: agent.environment,
489
+ name: agent.environment_name,
490
+ state: agent.environment_connected ? 'online' : 'offline',
491
+ },
492
+ }))
469
493
  .sort((left, right) => left.name === right.name
470
494
  ? left.identity.localeCompare(right.identity)
471
495
  : left.name.localeCompare(right.name));
472
496
  }
473
- function readAgents(alive) {
474
- const stream = alive.deps.stream();
475
- if (stream?.agents === undefined || !alive.deps.online() || alive.held.now().agentsReading)
476
- return;
477
- const { agents: read } = stream;
478
- const { agentListeners } = alive;
479
- alive.held.set({ agentsReading: true });
480
- void alive.deps.recovery
481
- .attempt('agents', async () => {
482
- if (!alive.deps.online() || (!agentListeners.waiting() && !agentListeners.told())) {
483
- return 'done';
484
- }
485
- const listed = await retrying(alive.deps.retries, 'conversations', AGENTS, read)();
486
- if (listed === null)
487
- return 'done';
488
- alive.held.set({ listedAgents: listed });
489
- emitAgents(alive);
490
- return 'done';
491
- })
492
- .finally(() => {
493
- alive.held.set({ agentsReading: false });
494
- });
495
- }
496
497
  function emitAgents(alive) {
497
- if (alive.deps.closed() || !alive.agentListeners.told())
498
+ const { agentListeners, listedListeners } = alive;
499
+ if (alive.deps.closed() || (!agentListeners.told() && !listedListeners.told()))
498
500
  return;
499
- shownList(alive, (list) => {
500
- const value = agents(alive, list);
501
+ shownListing(alive, (listing) => {
502
+ const value = agentsOf(listing);
501
503
  const shown = JSON.stringify(value);
502
- if (shown === alive.held.now().agentsShown)
503
- return;
504
- alive.held.set({ agentsShown: shown });
505
- alive.agentListeners.tell(value);
504
+ if (shown !== alive.held.now().agentsShown) {
505
+ alive.held.set({ agentsShown: shown });
506
+ agentListeners.tell(value);
507
+ }
508
+ const listed = JSON.stringify(listing.agents);
509
+ if (listed !== alive.held.now().listedShown) {
510
+ alive.held.set({ listedShown: listed });
511
+ listedListeners.tell(structuredClone(listing.agents));
512
+ }
506
513
  });
507
514
  }
508
515
  function setLive(alive, conversation, next) {
@@ -516,11 +523,11 @@ function setLive(alive, conversation, next) {
516
523
  after.asked !== before.asked)
517
524
  tellSoon(alive);
518
525
  }
519
- function retiredChat(conversation, list) {
526
+ function retiredChat(conversation, listing) {
520
527
  if (!conversation.startsWith(NEW_CHAT))
521
528
  return false;
522
529
  const agent = conversation.slice(NEW_CHAT.length);
523
- return list.some((entry) => entry.agent === agent && entry.agentRetired);
530
+ return listing.agents.some((one) => one.id === agent && one.retired_at !== null);
524
531
  }
525
532
  function live(alive, conversation, change) {
526
533
  alive.factsHeard(conversation, FACTS.filter((one) => one in change));
@@ -573,19 +580,13 @@ function handReplicated(alive) {
573
580
  }
574
581
  async function remove(alive, leaving) {
575
582
  await alive.deps.durably(async (local) => {
576
- const kept = new Set([
577
- ...(await local.conversations(leaving)).map(({ conversation }) => conversation),
578
- ...(await local.chatDrafts())
579
- .filter(({ conversation }) => leaving.includes(conversation))
580
- .map(({ conversation }) => conversation),
581
- ]);
582
- await local.dropConversations(leaving);
583
+ const kept = new Set(await local.dropConversations(leaving));
583
584
  const changed = [];
584
585
  for (const conversation of leaving) {
585
586
  if (kept.has(conversation) || alive.liveHeld(conversation) !== undefined) {
586
587
  changed.push(conversation);
587
588
  }
588
- shownDraft(alive, conversation, null);
589
+ heldInViews(alive, conversation, { draft: null, dismissed: null });
589
590
  alive.forget(conversation);
590
591
  setLive(alive, conversation, null);
591
592
  await slide(alive, local, conversation, null);
@@ -614,7 +615,7 @@ async function readsOf(local, conversations) {
614
615
  return new Map((await local.conversations(conversations)).map((held) => [held.conversation, held]));
615
616
  }
616
617
  function retiredDrafts(alive, list) {
617
- if (!list.some((entry) => entry.agentRetired))
618
+ if (!list.agents.some((agent) => agent.retired_at !== null))
618
619
  return;
619
620
  void alive.deps
620
621
  .local()
@@ -810,19 +811,27 @@ function heard(alive, event) {
810
811
  emit(alive, conversation);
811
812
  }
812
813
  async function kept(alive, local, conversation, following) {
813
- const shown = following === null || empty(following) ? null : following;
814
- if (shown === null)
814
+ const draft = following === null || empty(following) ? null : following;
815
+ if (draft === null)
815
816
  await local.dropChatDraft(conversation);
816
817
  else
817
- await local.keepChatDrafts([shown]);
818
- shownDraft(alive, conversation, shown);
818
+ await local.keepChatDrafts([draft]);
819
+ heldInViews(alive, conversation, { draft });
819
820
  }
820
- function shownDraft(alive, conversation, draft) {
821
+ function heldInViews(alive, conversation, held) {
821
822
  for (const view of alive.views()) {
822
823
  if (view.conversation === conversation)
823
- view.held.set({ draft });
824
+ view.held.set(held);
824
825
  }
825
826
  }
827
+ async function factOf(local, name) {
828
+ return Object.fromEntries((await local.conversationFacts())
829
+ .filter((fact) => fact.name === name)
830
+ .map(({ conversation, value }) => [conversation, value]));
831
+ }
832
+ async function dismissedIn(local, conversation) {
833
+ return (await factOf(local, 'dismissedSuggestion'))[conversation] ?? null;
834
+ }
826
835
  async function draftAt(local, conversation) {
827
836
  return (await local.chatDraft(conversation)) ?? undefined;
828
837
  }
@@ -853,9 +862,9 @@ function moved(alive, agent, operation, to) {
853
862
  : [{ ...unsent, conversation: to }];
854
863
  const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
855
864
  await local.keepChatDrafts([left, ...taken]);
856
- shownDraft(alive, from, left);
865
+ heldInViews(alive, from, { draft: left });
857
866
  for (const one of taken)
858
- shownDraft(alive, to, one);
867
+ heldInViews(alive, to, { draft: one });
859
868
  alive.deps.publish(conversationChangeSet([from, to]));
860
869
  });
861
870
  }
@@ -886,7 +895,7 @@ async function submitted(alive, conversation, operation, draft) {
886
895
  });
887
896
  return submission;
888
897
  }
889
- async function dispatched(alive, conversation, submission) {
898
+ async function dispatched(alive, conversation, submission, resent = false) {
890
899
  const dispatch = alive.sendingBegan(submission.operation);
891
900
  try {
892
901
  const result = await invoke(alive, 'conversations/send', conversation, submission.operation, { text: submission.text }, { step: 'sending', instance: `message ${submission.operation}` });
@@ -897,6 +906,7 @@ async function dispatched(alive, conversation, submission) {
897
906
  alive.deps.unanswered();
898
907
  return result;
899
908
  }
909
+ alive.deps.heard(submission.operation, resent);
900
910
  const turn = answered(result, 'message_id');
901
911
  await withDraft(alive, conversation, async (current, local) => {
902
912
  const held = current?.submitted?.find((one) => one.operation === submission.operation);
@@ -938,7 +948,7 @@ export function conversationSurface(alive) {
938
948
  return alive.deps.durably(async (local) => {
939
949
  const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before, phrase);
940
950
  const total = asked.total ? await local.messageCount(conversation, phrase) : null;
941
- return structuredClone(viewOf(alive, conversation, messages, frameOf(asked, messages, more, total), (await local.chatDraft(conversation)) ?? null, await endedIn(alive, conversation)));
951
+ return structuredClone(viewOf(alive, conversation, messages, frameOf(asked, messages, more, total), (await local.chatDraft(conversation)) ?? null, await endedIn(alive, conversation), await dismissedIn(local, conversation)));
942
952
  });
943
953
  },
944
954
  async create(input) {
@@ -946,7 +956,7 @@ export function conversationSurface(alive) {
946
956
  const conversation = answered(result, 'conversation_id');
947
957
  if (conversation === null)
948
958
  return result;
949
- const listed = (await heldList(alive)) ?? [];
959
+ const listed = (await heldListing(alive))?.conversations ?? [];
950
960
  if (!listed.some((entry) => entry.conversation === conversation)) {
951
961
  alive.creating(conversation);
952
962
  }
@@ -1019,33 +1029,44 @@ export function conversationSurface(alive) {
1019
1029
  return alive.deps.durably((local) => local.fact('chatPins', [...figures]));
1020
1030
  },
1021
1031
  async openedTurns() {
1022
- return (await (await alive.deps.local()).facts()).openedTurns;
1032
+ return factOf(await alive.deps.local(), 'openedTurn');
1023
1033
  },
1024
1034
  openTurn(conversation, turn) {
1035
+ return alive.deps.durably((local) => local.keepConversationFact({ conversation, name: 'openedTurn', value: turn }));
1036
+ },
1037
+ dismissSuggestion(conversation, suggestion) {
1025
1038
  return alive.deps.durably(async (local) => {
1026
- const { openedTurns } = await local.facts();
1027
- await local.fact('openedTurns', { ...openedTurns, [conversation]: turn });
1039
+ if ((await dismissedIn(local, conversation)) === suggestion)
1040
+ return;
1041
+ await local.keepConversationFact({
1042
+ conversation,
1043
+ name: 'dismissedSuggestion',
1044
+ value: suggestion,
1045
+ });
1046
+ heldInViews(alive, conversation, { dismissed: suggestion });
1047
+ alive.deps.publish(conversationChangeSet([conversation]));
1028
1048
  });
1029
1049
  },
1030
1050
  };
1031
1051
  }
1052
+ function rewritten(alive, conversation, text, revision = chatDraftRevision()) {
1053
+ return withDraft(alive, conversation, (current) => ({
1054
+ conversation,
1055
+ text: text(current?.text ?? ''),
1056
+ revision,
1057
+ ...(current === undefined ? {} : pending(current)),
1058
+ }));
1059
+ }
1032
1060
  export function chatDraftSurface(alive) {
1033
1061
  return {
1034
- write(conversation, draft, revision = chatDraftRevision()) {
1035
- return withDraft(alive, conversation, (current) => ({
1036
- conversation,
1037
- text: draft.text,
1038
- revision,
1039
- ...(current === undefined ? {} : pending(current)),
1040
- }));
1062
+ write(conversation, draft, revision) {
1063
+ return rewritten(alive, conversation, () => draft.text, revision);
1041
1064
  },
1042
1065
  prepend(conversation, draft) {
1043
- return withDraft(alive, conversation, (current) => ({
1044
- conversation,
1045
- ...(current === undefined ? { text: draft.text } : prepended(current, draft)),
1046
- revision: chatDraftRevision(),
1047
- ...(current === undefined ? {} : pending(current)),
1048
- }));
1066
+ return rewritten(alive, conversation, (held) => joinedDraft(draft.text, held));
1067
+ },
1068
+ append(conversation, draft) {
1069
+ return rewritten(alive, conversation, (held) => joinedDraft(held, draft.text));
1049
1070
  },
1050
1071
  discard(conversation) {
1051
1072
  return withDraft(alive, conversation, (current) => current === undefined
@@ -1055,31 +1076,34 @@ export function chatDraftSurface(alive) {
1055
1076
  };
1056
1077
  }
1057
1078
  export async function conversationsOpened(alive, local) {
1058
- const { conversationList, conversationCursor } = await local.facts();
1059
- if (conversationList === null || alive.known())
1079
+ const { conversationList, agentList, conversationCursor } = await local.facts();
1080
+ if (conversationList === null || agentList === null || alive.known())
1060
1081
  return;
1061
1082
  alive.deps.stream()?.hold(conversationCursor);
1062
1083
  conversationsLearned(alive);
1063
1084
  }
1085
+ export function reconcileOpening(alive) {
1086
+ alive.held.set({ openingRead: true });
1087
+ return reconcileConversations(alive);
1088
+ }
1064
1089
  export async function reconcileConversations(alive) {
1065
1090
  if (!alive.known())
1066
1091
  return;
1067
1092
  const local = await alive.deps.local();
1068
- const [held, kept, { conversationList }] = await Promise.all([
1093
+ const [held, stored, { conversationList, agentList }] = await Promise.all([
1069
1094
  local.conversations(),
1070
- local.chatDrafts(),
1095
+ local.storedConversations(),
1071
1096
  local.facts(),
1072
1097
  ]);
1073
1098
  const list = conversationList ?? [];
1099
+ const listing = { conversations: list, agents: agentList ?? [] };
1074
1100
  const entries = new Map(list.map((entry) => [entry.conversation, entry]));
1075
1101
  const named = new Set(entries.keys());
1076
1102
  const reads = new Map(held.map((one) => [one.conversation, one]));
1077
1103
  const { replicated } = alive.held.now();
1078
- const leaving = [
1079
- ...new Set([...replicated, ...reads.keys(), ...kept.map(({ conversation }) => conversation)]),
1080
- ].filter((conversation) => !named.has(conversation) &&
1104
+ const leaving = [...new Set([...replicated, ...stored])].filter((conversation) => !named.has(conversation) &&
1081
1105
  !alive.isCreating(conversation) &&
1082
- (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation, list)));
1106
+ (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation, listing)));
1083
1107
  for (const conversation of named) {
1084
1108
  if (!replicated.has(conversation) &&
1085
1109
  missed(alive, conversation, reads.get(conversation), entries.get(conversation))) {
@@ -1114,6 +1138,17 @@ export async function catchUpConversations(alive, progress) {
1114
1138
  if (!alive.current())
1115
1139
  progress.complete = false;
1116
1140
  }
1141
+ export async function holdsSubmission(alive, operation) {
1142
+ if (alive.sending(operation))
1143
+ return true;
1144
+ const local = await alive.deps.local();
1145
+ for (const { conversation } of await local.chatDrafts()) {
1146
+ const held = (await local.chatDraft(conversation))?.submitted;
1147
+ if (held?.some((one) => one.operation === operation && one.turn === null) === true)
1148
+ return true;
1149
+ }
1150
+ return false;
1151
+ }
1117
1152
  export async function resendConversations(alive, progress) {
1118
1153
  const local = await alive.deps.local();
1119
1154
  const tried = new Set();
@@ -1125,7 +1160,7 @@ export async function resendConversations(alive, progress) {
1125
1160
  if (alive.deps.closed())
1126
1161
  return;
1127
1162
  tried.add(submission.operation);
1128
- const result = await dispatched(alive, conversation, submission);
1163
+ const result = await dispatched(alive, conversation, submission, true);
1129
1164
  if (result.kind === 'revoked-authorization')
1130
1165
  return;
1131
1166
  if (result.kind === 'transport-uncertainty') {
@@ -1151,6 +1186,7 @@ export function watchConversation(alive, conversation, options, deliver) {
1151
1186
  const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
1152
1187
  held.set({ draft: (await local.chatDraft(conversation)) ?? null });
1153
1188
  held.set({ ended: await endedIn(alive, conversation) });
1189
+ held.set({ dismissed: await dismissedIn(local, conversation) });
1154
1190
  held.set({ window: messages });
1155
1191
  held.set({ more });
1156
1192
  held.set({ total: asked.total ? await local.messageCount(conversation) : null });
@@ -1158,9 +1194,10 @@ export function watchConversation(alive, conversation, options, deliver) {
1158
1194
  .then(() => {
1159
1195
  if (alive.deps.closed() || !view.admit(watcher))
1160
1196
  return;
1161
- const { window, draft, ended } = held.now();
1197
+ const { window, draft, ended, dismissed } = held.now();
1162
1198
  const shown = window ?? [];
1163
- watcher.notify(viewOf(alive, conversation, shown, sharedFrame(view, shown), draft, ended));
1199
+ const frame = sharedFrame(view, shown);
1200
+ watcher.notify(viewOf(alive, conversation, shown, frame, draft, ended, dismissed));
1164
1201
  })
1165
1202
  .catch(() => { });
1166
1203
  return () => {
@@ -1176,9 +1213,13 @@ export function watchConversationList(alive, deliver) {
1176
1213
  export function watchConversationAgents(alive, deliver) {
1177
1214
  alive.agentListeners.join(deliver);
1178
1215
  conversationsLearned(alive);
1179
- readAgents(alive);
1180
1216
  return () => alive.agentListeners.leave(deliver);
1181
1217
  }
1218
+ export function watchListedAgents(alive, deliver) {
1219
+ alive.listedListeners.join(deliver);
1220
+ conversationsLearned(alive);
1221
+ return () => alive.listedListeners.leave(deliver);
1222
+ }
1182
1223
  export function conversationsChanged(alive, set) {
1183
1224
  for (const conversation of set.conversations)
1184
1225
  emit(alive, conversation);
@@ -1190,26 +1231,32 @@ export function receiveConversationEvent(alive, event) {
1190
1231
  if (event.whole)
1191
1232
  alive.relistBegun();
1192
1233
  if (alive.relisting())
1193
- alive.relistHeard(event.entries);
1234
+ alive.relistHeard(event.entries, event.agents);
1194
1235
  if (event.more && alive.relisting())
1195
1236
  return;
1196
1237
  const whole = alive.relistTaken();
1197
1238
  listChanged(alive, {
1198
1239
  replacing: whole !== null,
1199
- set: whole === null ? event.entries : [...whole.values()],
1240
+ set: whole === null ? event.entries : [...whole.conversations.values()],
1241
+ agents: whole === null ? event.agents : [...whole.agents.values()],
1200
1242
  gone: null,
1201
1243
  ...(event.more ? { paged: true } : { cursor: event.cursor }),
1202
1244
  });
1203
1245
  return;
1204
1246
  }
1205
1247
  if (event.kind === 'listed') {
1206
- alive.relistHeard([event.entry]);
1207
- listChanged(alive, { replacing: false, set: [event.entry], gone: null });
1248
+ alive.relistHeard([event.entry], []);
1249
+ listChanged(alive, { replacing: false, set: [event.entry], agents: [], gone: null });
1250
+ return;
1251
+ }
1252
+ if (event.kind === 'agent') {
1253
+ alive.relistHeard([], [event.agent]);
1254
+ listChanged(alive, { replacing: false, set: [], agents: [event.agent], gone: null });
1208
1255
  return;
1209
1256
  }
1210
1257
  if (event.kind === 'unlisted') {
1211
1258
  alive.relistDropped(event.conversation);
1212
- listChanged(alive, { replacing: false, set: [], gone: event.conversation });
1259
+ listChanged(alive, { replacing: false, set: [], agents: [], gone: event.conversation });
1213
1260
  return;
1214
1261
  }
1215
1262
  heard(alive, event);
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,SAAS,EACT,cAAc,EACd,MAAM,EAEP,MAAM,cAAc,CAAC;AAOtB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAkB/C,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,GAAG,IAAI,CAsB1E;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,SAAS,cAAc,EAAE,EAChC,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,GACvB,OAAO,CAAC,SAAS,cAAc,EAAE,CAAC,CA0CpC;AAYD,wBAAsB,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,iBAI1F;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,aAAa,EACvB,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,SAAS,GAAG,IAAI,GACtB,OAAO,CAAC,SAAS,cAAc,EAAE,GAAG,IAAI,CAAC,CAkE3C"}
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,SAAS,EACT,cAAc,EACd,MAAM,EAEP,MAAM,cAAc,CAAC;AAOtB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAkB/C,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,GAAG,IAAI,CAsB1E;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,SAAS,cAAc,EAAE,EAChC,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,GACvB,OAAO,CAAC,SAAS,cAAc,EAAE,CAAC,CA0CpC;AAYD,wBAAsB,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,iBAI1F;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,aAAa,EACvB,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,SAAS,GAAG,IAAI,GACtB,OAAO,CAAC,SAAS,cAAc,EAAE,GAAG,IAAI,CAAC,CA+D3C"}