@agentshouse/mdruntime 0.1.0-cbbae1fff01c6 → 0.1.0-cc00e9289ddf4

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 (200) hide show
  1. package/README.md +17 -15
  2. package/dist/acceptance.d.ts.map +1 -1
  3. package/dist/acceptance.js +14 -23
  4. package/dist/act-lanes.d.ts +30 -0
  5. package/dist/act-lanes.d.ts.map +1 -0
  6. package/dist/act-lanes.js +225 -0
  7. package/dist/actions.d.ts +8 -2
  8. package/dist/actions.d.ts.map +1 -1
  9. package/dist/actions.js +114 -63
  10. package/dist/adapter.d.ts +30 -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 +8 -18
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +27 -15
  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 +15 -13
  26. package/dist/conversation-live.d.ts +23 -8
  27. package/dist/conversation-live.d.ts.map +1 -1
  28. package/dist/conversation-live.js +21 -9
  29. package/dist/conversation-row.d.ts +2 -0
  30. package/dist/conversation-row.d.ts.map +1 -1
  31. package/dist/conversation-row.js +2 -0
  32. package/dist/conversation.d.ts +27 -5
  33. package/dist/conversation.d.ts.map +1 -1
  34. package/dist/conversation.js +279 -168
  35. package/dist/discovery.d.ts.map +1 -1
  36. package/dist/discovery.js +8 -6
  37. package/dist/durability.d.ts +4 -0
  38. package/dist/durability.d.ts.map +1 -1
  39. package/dist/durability.js +4 -0
  40. package/dist/entry.js +1 -1
  41. package/dist/expectation.d.ts +25 -14
  42. package/dist/expectation.d.ts.map +1 -1
  43. package/dist/expectation.js +51 -20
  44. package/dist/failure.d.ts +2 -0
  45. package/dist/failure.d.ts.map +1 -1
  46. package/dist/failure.js +3 -0
  47. package/dist/figures.d.ts.map +1 -1
  48. package/dist/figures.js +4 -2
  49. package/dist/file.d.ts.map +1 -1
  50. package/dist/file.js +2 -1
  51. package/dist/host.d.ts +1 -0
  52. package/dist/host.d.ts.map +1 -1
  53. package/dist/host.js +39 -11
  54. package/dist/index.d.ts +11 -8
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +9 -6
  57. package/dist/install.d.ts +10 -4
  58. package/dist/install.d.ts.map +1 -1
  59. package/dist/install.js +45 -46
  60. package/dist/lifecycle.d.ts.map +1 -1
  61. package/dist/lifecycle.js +22 -16
  62. package/dist/listing.d.ts +7 -3
  63. package/dist/listing.d.ts.map +1 -1
  64. package/dist/listing.js +26 -13
  65. package/dist/live.d.ts +5 -3
  66. package/dist/live.d.ts.map +1 -1
  67. package/dist/live.js +71 -30
  68. package/dist/locks.d.ts.map +1 -1
  69. package/dist/locks.js +2 -3
  70. package/dist/lost.d.ts.map +1 -1
  71. package/dist/lost.js +2 -3
  72. package/dist/media.d.ts.map +1 -1
  73. package/dist/media.js +7 -10
  74. package/dist/memory.d.ts +3 -1
  75. package/dist/memory.d.ts.map +1 -1
  76. package/dist/memory.js +50 -22
  77. package/dist/messages.d.ts +12 -3
  78. package/dist/messages.d.ts.map +1 -1
  79. package/dist/messages.js +24 -10
  80. package/dist/models.d.ts.map +1 -1
  81. package/dist/models.js +5 -8
  82. package/dist/observation.d.ts +27 -12
  83. package/dist/observation.d.ts.map +1 -1
  84. package/dist/observation.js +173 -69
  85. package/dist/opening.d.ts.map +1 -1
  86. package/dist/opening.js +3 -9
  87. package/dist/outcome.d.ts +7 -0
  88. package/dist/outcome.d.ts.map +1 -1
  89. package/dist/outcome.js +23 -2
  90. package/dist/overlay.d.ts +3 -0
  91. package/dist/overlay.d.ts.map +1 -1
  92. package/dist/overlay.js +8 -4
  93. package/dist/page-metadata.d.ts +8 -0
  94. package/dist/page-metadata.d.ts.map +1 -0
  95. package/dist/page-metadata.js +19 -0
  96. package/dist/parts.d.ts +2 -9
  97. package/dist/parts.d.ts.map +1 -1
  98. package/dist/parts.js +6 -14
  99. package/dist/preparation.d.ts +27 -6
  100. package/dist/preparation.d.ts.map +1 -1
  101. package/dist/preparation.js +137 -56
  102. package/dist/presence.d.ts +7 -1
  103. package/dist/presence.d.ts.map +1 -1
  104. package/dist/presence.js +22 -9
  105. package/dist/queries.d.ts +2 -2
  106. package/dist/queries.d.ts.map +1 -1
  107. package/dist/queries.js +62 -24
  108. package/dist/questions.d.ts +0 -5
  109. package/dist/questions.d.ts.map +1 -1
  110. package/dist/questions.js +0 -7
  111. package/dist/queue.d.ts +2 -2
  112. package/dist/queue.d.ts.map +1 -1
  113. package/dist/queue.js +25 -30
  114. package/dist/reach.d.ts +2 -3
  115. package/dist/reach.d.ts.map +1 -1
  116. package/dist/reach.js +75 -21
  117. package/dist/react.d.ts +7 -5
  118. package/dist/react.d.ts.map +1 -1
  119. package/dist/react.js +28 -5
  120. package/dist/readiness.d.ts +9 -3
  121. package/dist/readiness.d.ts.map +1 -1
  122. package/dist/readiness.js +3 -5
  123. package/dist/reads.d.ts +3 -1
  124. package/dist/reads.d.ts.map +1 -1
  125. package/dist/reads.js +5 -1
  126. package/dist/recovery.d.ts +3 -1
  127. package/dist/recovery.d.ts.map +1 -1
  128. package/dist/recovery.js +29 -7
  129. package/dist/refusal.d.ts.map +1 -1
  130. package/dist/refusal.js +1 -3
  131. package/dist/retried.d.ts +30 -0
  132. package/dist/retried.d.ts.map +1 -0
  133. package/dist/retried.js +72 -0
  134. package/dist/room-draft.d.ts +2 -2
  135. package/dist/room-draft.d.ts.map +1 -1
  136. package/dist/room-draft.js +26 -21
  137. package/dist/rows.d.ts +2 -0
  138. package/dist/rows.d.ts.map +1 -1
  139. package/dist/rows.js +3 -0
  140. package/dist/runtime.d.ts +2 -2
  141. package/dist/runtime.d.ts.map +1 -1
  142. package/dist/save.d.ts +1 -0
  143. package/dist/save.d.ts.map +1 -1
  144. package/dist/save.js +105 -55
  145. package/dist/saves.d.ts.map +1 -1
  146. package/dist/saves.js +3 -6
  147. package/dist/seed.d.ts.map +1 -1
  148. package/dist/seed.js +2 -2
  149. package/dist/shown.d.ts +3 -2
  150. package/dist/shown.d.ts.map +1 -1
  151. package/dist/shown.js +58 -39
  152. package/dist/socket/lanes.d.ts +12 -4
  153. package/dist/socket/lanes.d.ts.map +1 -1
  154. package/dist/socket/lanes.js +34 -8
  155. package/dist/socket.d.ts +3 -3
  156. package/dist/socket.d.ts.map +1 -1
  157. package/dist/socket.js +3 -9
  158. package/dist/source.d.ts +0 -7
  159. package/dist/source.d.ts.map +1 -1
  160. package/dist/store/installs.d.ts +2 -2
  161. package/dist/store/installs.d.ts.map +1 -1
  162. package/dist/store/installs.js +10 -4
  163. package/dist/store/readiness.d.ts +5 -4
  164. package/dist/store/readiness.d.ts.map +1 -1
  165. package/dist/store/readiness.js +28 -12
  166. package/dist/store/records.d.ts +11 -4
  167. package/dist/store/records.d.ts.map +1 -1
  168. package/dist/store/records.js +56 -20
  169. package/dist/store/rooms-shown.d.ts +8 -0
  170. package/dist/store/rooms-shown.d.ts.map +1 -0
  171. package/dist/store/rooms-shown.js +13 -0
  172. package/dist/store.d.ts +9 -6
  173. package/dist/store.d.ts.map +1 -1
  174. package/dist/store.js +11 -2
  175. package/dist/stored.d.ts +24 -13
  176. package/dist/stored.d.ts.map +1 -1
  177. package/dist/stored.js +73 -17
  178. package/dist/surface.d.ts +4 -3
  179. package/dist/surface.d.ts.map +1 -1
  180. package/dist/surface.js +12 -13
  181. package/dist/sync.d.ts.map +1 -1
  182. package/dist/sync.js +31 -17
  183. package/dist/tables.d.ts +14 -7
  184. package/dist/tables.d.ts.map +1 -1
  185. package/dist/tables.js +36 -15
  186. package/dist/tallies.d.ts.map +1 -1
  187. package/dist/tallies.js +1 -2
  188. package/dist/wire.d.ts +40 -3
  189. package/dist/wire.d.ts.map +1 -1
  190. package/dist/wire.js +11 -1
  191. package/package.json +3 -3
  192. package/dist/attempt.d.ts +0 -16
  193. package/dist/attempt.d.ts.map +0 -1
  194. package/dist/attempt.js +0 -70
  195. package/dist/job.d.ts +0 -5
  196. package/dist/job.d.ts.map +0 -1
  197. package/dist/job.js +0 -7
  198. package/dist/log.d.ts +0 -6
  199. package/dist/log.d.ts.map +0 -1
  200. package/dist/log.js +0 -14
@@ -11,6 +11,7 @@ import { runningLine } from './parts.js';
11
11
  import { record, text } from './read.js';
12
12
  import { openQuestion, questionAsked } from './questions.js';
13
13
  import { untilAnswered } from './recovery.js';
14
+ import { retrying } from './retried.js';
14
15
  import { capped, FROM_START, messageOrder, precedes } from './windows.js';
15
16
  export function unheldView(conversation) {
16
17
  return {
@@ -25,12 +26,17 @@ export function unheldView(conversation) {
25
26
  starting: null,
26
27
  state: null,
27
28
  link: null,
29
+ deliveryWaiting: [],
28
30
  deliveryUnknown: [],
29
31
  chatDraft: null,
32
+ dismissedSuggestion: null,
33
+ pins: [],
30
34
  };
31
35
  }
32
36
  export const SESSION_STARTED = 'Session started';
33
37
  const NEW_CHAT = 'new:';
38
+ const LISTING = 'listing';
39
+ export const CATCHING_UP = 'catching-up';
34
40
  export const HISTORY_CONVERSATIONS = 64;
35
41
  export function newChat(agent) {
36
42
  return `${NEW_CHAT}${agent}`;
@@ -74,7 +80,7 @@ function frameOf(asked, messages, more, total) {
74
80
  };
75
81
  }
76
82
  function newestOf(arriving, from) {
77
- return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
83
+ return arriving.reduce((top, message) => (top === null || message.change > top ? message.change : top), from);
78
84
  }
79
85
  function acceptedMessage(value) {
80
86
  if (typeof value !== 'object' || value === null)
@@ -83,6 +89,7 @@ function acceptedMessage(value) {
83
89
  if (typeof record.identity !== 'string' ||
84
90
  typeof record.turn !== 'string' ||
85
91
  typeof record.position !== 'string' ||
92
+ typeof record.change !== 'string' ||
86
93
  typeof record.text !== 'string') {
87
94
  return null;
88
95
  }
@@ -90,6 +97,7 @@ function acceptedMessage(value) {
90
97
  identity: record.identity,
91
98
  turn: record.turn,
92
99
  position: record.position,
100
+ change: record.change,
93
101
  text: record.text,
94
102
  message: structuredClone(record.message),
95
103
  };
@@ -191,17 +199,17 @@ function withSubmitted(draft, submitted) {
191
199
  const { submitted: _held, ...unsent } = draft;
192
200
  return submitted.length === 0 ? unsent : { ...unsent, submitted };
193
201
  }
194
- function prepended(draft, said) {
195
- return { text: [said.text, draft.text].filter((one) => one !== '').join('\n') };
202
+ export function joinedDraft(first, second) {
203
+ return [first, second].filter((one) => one !== '').join('\n');
196
204
  }
197
205
  function returned(draft, submission, others) {
198
206
  return withSubmitted({
199
207
  conversation: draft.conversation,
200
- ...prepended(draft, submission),
208
+ text: joinedDraft(submission.text, draft.text),
201
209
  revision: chatDraftRevision(),
202
210
  }, others);
203
211
  }
204
- function viewOf(alive, conversation, held, frame, draft, ended) {
212
+ function viewOf(alive, conversation, held, frame, { draft, ended, dismissed, pins }) {
205
213
  const facts = alive.liveOf(conversation);
206
214
  return {
207
215
  conversation,
@@ -221,8 +229,11 @@ function viewOf(alive, conversation, held, frame, draft, ended) {
221
229
  starting: facts.starting,
222
230
  state: sessionOf(facts, held, ended),
223
231
  link: facts.link === null ? null : { ...facts.link },
232
+ deliveryWaiting: [...facts.deliveryWaiting],
224
233
  deliveryUnknown: [...facts.deliveryUnknown],
225
234
  chatDraft: draft === null || unwritten(draft) ? null : { text: draft.text, revision: draft.revision },
235
+ dismissedSuggestion: dismissed,
236
+ pins: [...pins],
226
237
  };
227
238
  }
228
239
  function sessionOf(facts, held, ended) {
@@ -242,13 +253,15 @@ function emit(alive, conversation) {
242
253
  if (alive.deps.closed())
243
254
  return;
244
255
  for (const view of alive.views()) {
245
- const { window, draft, ended } = view.held.now();
256
+ const held = view.held.now();
257
+ const { window } = held;
246
258
  if (view.conversation !== conversation || window === null)
247
259
  continue;
248
260
  const ready = view.ready();
249
261
  if (ready.length === 0)
250
262
  continue;
251
- const value = viewOf(alive, conversation, window, sharedFrame(view, window), draft, ended);
263
+ const frame = sharedFrame(view, window);
264
+ const value = viewOf(alive, conversation, window, frame, held);
252
265
  for (const watcher of ready)
253
266
  watcher.notify(value);
254
267
  }
@@ -294,66 +307,90 @@ function rowOf(alive, conversation) {
294
307
  };
295
308
  }
296
309
  async function entries(alive) {
297
- const list = await heldList(alive);
298
- if (list === null)
310
+ const listing = await heldListing(alive);
311
+ if (listing === null)
312
+ return null;
313
+ const list = listing.conversations;
314
+ const agents = new Map(listing.agents.map((agent) => [agent.id, agent]));
315
+ if (list.some((entry) => !agents.has(entry.agent)))
299
316
  return null;
300
317
  const held = list.length === 0
301
318
  ? []
302
319
  : await alive.deps.durably((local) => local.conversationRows(list.map(({ conversation }) => conversation)));
303
320
  const rows = new Map(held.map((one) => [one.conversation, one]));
304
- return list.map((entry) => ({
305
- conversation: entry.conversation,
306
- agent: { identity: entry.agent, name: entry.agentName, retired: entry.agentRetired },
307
- name: entry.name,
308
- created: entry.created,
309
- finished: entry.finished,
310
- outcome: entry.outcome,
311
- waiting: entry.waiting,
312
- ...rowOf(alive, entry.conversation),
313
- ...heldFacts(rows.get(entry.conversation), entry),
314
- }));
321
+ return list.map((entry) => {
322
+ const agent = agents.get(entry.agent);
323
+ return {
324
+ conversation: entry.conversation,
325
+ agent: { identity: agent.id, name: agent.name, retired: agent.retired_at !== null },
326
+ name: entry.name,
327
+ created: entry.created,
328
+ finished: entry.finished,
329
+ outcome: entry.outcome,
330
+ waiting: entry.waiting,
331
+ subject: entry.subject,
332
+ ...rowOf(alive, entry.conversation),
333
+ ...heldFacts(rows.get(entry.conversation), entry),
334
+ };
335
+ });
315
336
  }
316
- async function heldList(alive) {
317
- const stored = (await (await alive.deps.local()).facts()).conversationList;
318
- if (stored !== null)
319
- return stored;
320
- return !alive.known() && alive.deps.stream() !== undefined && alive.deps.online() ? null : [];
337
+ async function heldListing(alive) {
338
+ const { conversationList, agentList } = await (await alive.deps.local()).facts();
339
+ if (conversationList !== null && agentList !== null)
340
+ return { conversations: conversationList, agents: agentList };
341
+ return !alive.known() && alive.deps.stream() !== undefined && alive.deps.online()
342
+ ? null
343
+ : { conversations: [], agents: [] };
321
344
  }
322
345
  async function endedIn(alive, conversation) {
323
- const list = await heldList(alive);
324
- return list?.find((entry) => entry.conversation === conversation)?.processEnded ?? null;
346
+ const listing = await heldListing(alive);
347
+ return (listing?.conversations.find((entry) => entry.conversation === conversation)?.processEnded ??
348
+ null);
349
+ }
350
+ function upserted(kept, set, key) {
351
+ const held = new Map(kept.map((one) => [key(one), one]));
352
+ for (const one of set)
353
+ held.set(key(one), { ...one });
354
+ return held;
325
355
  }
326
356
  function listChanged(alive, change) {
327
357
  void alive.deps
328
358
  .durably(async (local) => {
329
- const { conversationList, conversationCursor } = await local.facts();
330
- const kept = change.replacing ? [] : (conversationList ?? []);
331
- const held = new Map(kept.map((entry) => [entry.conversation, entry]));
332
- for (const entry of change.set) {
333
- held.set(entry.conversation, { ...entry });
359
+ const { conversationList, agentList, conversationCursor } = await local.facts();
360
+ const held = upserted(change.replacing ? [] : (conversationList ?? []), change.set, (entry) => entry.conversation);
361
+ for (const entry of change.set)
334
362
  alive.created(entry.conversation);
335
- }
336
363
  if (change.gone !== null)
337
364
  held.delete(change.gone);
338
365
  const list = [...held.values()];
366
+ const agents = [
367
+ ...upserted(change.replacing ? [] : (agentList ?? []), change.agents, (agent) => agent.id).values(),
368
+ ];
339
369
  if (JSON.stringify(list) !== JSON.stringify(conversationList)) {
340
370
  await local.fact('conversationList', list);
341
371
  }
372
+ if (JSON.stringify(agents) !== JSON.stringify(agentList)) {
373
+ await local.fact('agentList', agents);
374
+ }
342
375
  if (change.cursor !== undefined) {
343
376
  if (change.cursor !== conversationCursor) {
344
377
  await local.fact('conversationCursor', change.cursor);
345
378
  }
346
379
  alive.deps.stream()?.hold(change.cursor);
347
380
  }
348
- return list;
381
+ return { conversations: list, agents };
349
382
  })
350
- .catch(async () => (await (await alive.deps.local()).facts()).conversationList ?? [])
351
- .then((list) => {
383
+ .catch(async () => {
384
+ const { conversationList, agentList } = await (await alive.deps.local()).facts();
385
+ return { conversations: conversationList ?? [], agents: agentList ?? [] };
386
+ })
387
+ .then((listing) => {
352
388
  if (alive.deps.closed() || change.paged === true)
353
389
  return;
390
+ const list = listing.conversations;
354
391
  endedAnew(alive, list);
355
392
  if (change.cursor !== undefined) {
356
- alive.held.set({ listKnown: true });
393
+ alive.held.set({ listKnown: true, openingListed: true });
357
394
  settleBehind(alive, list);
358
395
  openingEnded(alive);
359
396
  alive.deps.settled();
@@ -361,11 +398,12 @@ function listChanged(alive, change) {
361
398
  emitList(alive);
362
399
  if (!alive.known())
363
400
  return;
364
- retiredDrafts(alive, list);
365
- const { replicated } = alive.held.now();
366
- if (change.cursor === undefined &&
367
- (list.length !== replicated.size ||
368
- list.some(({ conversation }) => !replicated.has(conversation)))) {
401
+ retiredDrafts(alive, listing);
402
+ const { replicated, openingRead } = alive.held.now();
403
+ if (change.cursor === undefined
404
+ ? list.length !== replicated.size ||
405
+ list.some(({ conversation }) => !replicated.has(conversation))
406
+ : openingRead) {
369
407
  alive.deps.again();
370
408
  }
371
409
  }, uncaught);
@@ -381,23 +419,23 @@ function endedAnew(alive, list) {
381
419
  emit(alive, view.conversation);
382
420
  }
383
421
  }
384
- function shownList(alive, work) {
385
- const reading = heldList(alive);
422
+ function shownListing(alive, work) {
423
+ const reading = heldListing(alive);
386
424
  alive.inOrder(async () => {
387
- const list = await reading.catch((error) => {
425
+ const listing = await reading.catch((error) => {
388
426
  uncaught(error);
389
427
  return null;
390
428
  });
391
- if (list === null || alive.deps.closed())
429
+ if (listing === null || alive.deps.closed())
392
430
  return;
393
- work(list);
431
+ work(listing);
394
432
  });
395
433
  }
396
434
  async function told(alive) {
397
435
  try {
398
436
  while (alive.held.now().listOwed && !alive.deps.closed()) {
399
437
  alive.held.set({ listOwed: false });
400
- const value = await untilAnswered(() => entries(alive), alive.closing);
438
+ const value = await untilAnswered(retrying(alive.deps.retries, 'conversations', LISTING, () => entries(alive)), alive.closing, () => true, alive.deps.recovery);
401
439
  if (alive.deps.closed())
402
440
  return;
403
441
  if (value === null)
@@ -435,57 +473,46 @@ export function conversationsLearned(alive) {
435
473
  return;
436
474
  if (alive.listeners.waiting())
437
475
  tellList(alive);
438
- if (!alive.agentListeners.waiting())
476
+ if (!alive.agentListeners.waiting() && !alive.listedListeners.waiting())
439
477
  return;
440
- shownList(alive, (list) => alive.agentListeners.welcome(agents(alive, list)));
441
- }
442
- function agents(alive, list) {
443
- const named = new Map();
444
- for (const agent of alive.held.now().listedAgents ?? [])
445
- named.set(agent.agent, agent);
446
- for (const entry of list) {
447
- named.set(entry.agent, { name: entry.agentName, retired: entry.agentRetired });
448
- }
449
- return [...named]
450
- .filter(([, agent]) => !agent.retired)
451
- .map(([identity, agent]) => ({ identity, name: agent.name }))
478
+ shownListing(alive, (listing) => {
479
+ alive.agentListeners.welcome(agentsOf(listing));
480
+ alive.listedListeners.welcome(structuredClone(listing.agents));
481
+ });
482
+ }
483
+ function agentsOf(listing) {
484
+ return listing.agents
485
+ .filter((agent) => agent.retired_at === null)
486
+ .map((agent) => ({
487
+ identity: agent.id,
488
+ name: agent.name,
489
+ cli: agent.cli,
490
+ environment: {
491
+ identity: agent.environment,
492
+ name: agent.environment_name,
493
+ state: agent.environment_connected ? 'online' : 'offline',
494
+ },
495
+ }))
452
496
  .sort((left, right) => left.name === right.name
453
497
  ? left.identity.localeCompare(right.identity)
454
498
  : left.name.localeCompare(right.name));
455
499
  }
456
- function readAgents(alive) {
457
- const stream = alive.deps.stream();
458
- if (stream?.agents === undefined || !alive.deps.online() || alive.held.now().agentsReading)
459
- return;
460
- const { agents: read } = stream;
461
- const { agentListeners } = alive;
462
- alive.held.set({ agentsReading: true });
463
- void alive.deps.recovery
464
- .attempt('agents', async () => {
465
- if (!alive.deps.online() || (!agentListeners.waiting() && !agentListeners.told())) {
466
- return 'done';
467
- }
468
- const listed = await read();
469
- if (listed === null)
470
- return 'done';
471
- alive.held.set({ listedAgents: listed });
472
- emitAgents(alive);
473
- return 'done';
474
- })
475
- .finally(() => {
476
- alive.held.set({ agentsReading: false });
477
- });
478
- }
479
500
  function emitAgents(alive) {
480
- if (alive.deps.closed() || !alive.agentListeners.told())
501
+ const { agentListeners, listedListeners } = alive;
502
+ if (alive.deps.closed() || (!agentListeners.told() && !listedListeners.told()))
481
503
  return;
482
- shownList(alive, (list) => {
483
- const value = agents(alive, list);
504
+ shownListing(alive, (listing) => {
505
+ const value = agentsOf(listing);
484
506
  const shown = JSON.stringify(value);
485
- if (shown === alive.held.now().agentsShown)
486
- return;
487
- alive.held.set({ agentsShown: shown });
488
- alive.agentListeners.tell(value);
507
+ if (shown !== alive.held.now().agentsShown) {
508
+ alive.held.set({ agentsShown: shown });
509
+ agentListeners.tell(value);
510
+ }
511
+ const listed = JSON.stringify(listing.agents);
512
+ if (listed !== alive.held.now().listedShown) {
513
+ alive.held.set({ listedShown: listed });
514
+ listedListeners.tell(structuredClone(listing.agents));
515
+ }
489
516
  });
490
517
  }
491
518
  function setLive(alive, conversation, next) {
@@ -499,11 +526,11 @@ function setLive(alive, conversation, next) {
499
526
  after.asked !== before.asked)
500
527
  tellSoon(alive);
501
528
  }
502
- function retiredChat(conversation, list) {
529
+ function retiredChat(conversation, listing) {
503
530
  if (!conversation.startsWith(NEW_CHAT))
504
531
  return false;
505
532
  const agent = conversation.slice(NEW_CHAT.length);
506
- return list.some((entry) => entry.agent === agent && entry.agentRetired);
533
+ return listing.agents.some((one) => one.id === agent && one.retired_at !== null);
507
534
  }
508
535
  function live(alive, conversation, change) {
509
536
  alive.factsHeard(conversation, FACTS.filter((one) => one in change));
@@ -524,6 +551,7 @@ function openingEnded(alive) {
524
551
  starting: facts.has('starting') ? QUIET.starting : held.starting,
525
552
  state: facts.has('state') ? QUIET.state : held.state,
526
553
  link: facts.has('link') ? QUIET.link : held.link,
554
+ deliveryWaiting: facts.has('deliveryWaiting') ? QUIET.deliveryWaiting : held.deliveryWaiting,
527
555
  deliveryUnknown: facts.has('deliveryUnknown') ? QUIET.deliveryUnknown : held.deliveryUnknown,
528
556
  });
529
557
  emit(alive, conversation);
@@ -555,19 +583,13 @@ function handReplicated(alive) {
555
583
  }
556
584
  async function remove(alive, leaving) {
557
585
  await alive.deps.durably(async (local) => {
558
- const kept = new Set([
559
- ...(await local.conversations(leaving)).map(({ conversation }) => conversation),
560
- ...(await local.chatDrafts())
561
- .filter(({ conversation }) => leaving.includes(conversation))
562
- .map(({ conversation }) => conversation),
563
- ]);
564
- await local.dropConversations(leaving);
586
+ const kept = new Set(await local.dropConversations(leaving));
565
587
  const changed = [];
566
588
  for (const conversation of leaving) {
567
589
  if (kept.has(conversation) || alive.liveHeld(conversation) !== undefined) {
568
590
  changed.push(conversation);
569
591
  }
570
- shownDraft(alive, conversation, null);
592
+ heldInViews(alive, conversation, { draft: null, dismissed: null });
571
593
  alive.forget(conversation);
572
594
  setLive(alive, conversation, null);
573
595
  await slide(alive, local, conversation, null);
@@ -596,7 +618,7 @@ async function readsOf(local, conversations) {
596
618
  return new Map((await local.conversations(conversations)).map((held) => [held.conversation, held]));
597
619
  }
598
620
  function retiredDrafts(alive, list) {
599
- if (!list.some((entry) => entry.agentRetired))
621
+ if (!list.agents.some((agent) => agent.retired_at !== null))
600
622
  return;
601
623
  void alive.deps
602
624
  .local()
@@ -639,14 +661,12 @@ function keep(alive, conversation, message) {
639
661
  if (!alive.held.now().replicated.has(conversation))
640
662
  return;
641
663
  const read = (await readsOf(local, [conversation])).get(conversation)?.read;
642
- const advances = read !== undefined &&
643
- !alive.behind(conversation) &&
644
- (read === null || message.position > read);
664
+ const advances = read !== undefined && !alive.behind(conversation) && (read === null || message.change > read);
645
665
  await sealed(alive, local, conversation, new Set([message.turn]));
646
666
  await local.keepMessages({
647
667
  conversation,
648
668
  messages: [message],
649
- ...(advances ? { read: { at: message.position, partial: false } } : {}),
669
+ ...(advances ? { read: { at: message.change, partial: false } } : {}),
650
670
  });
651
671
  sealTurns(alive, conversation, new Set([message.turn]));
652
672
  await slide(alive, local, conversation, [message]);
@@ -767,13 +787,16 @@ function heard(alive, event) {
767
787
  return;
768
788
  }
769
789
  if (event.kind === 'deliveries') {
770
- live(alive, conversation, { deliveryUnknown: [...event.unknown] });
790
+ live(alive, conversation, {
791
+ deliveryWaiting: [...event.waiting],
792
+ deliveryUnknown: [...event.unknown],
793
+ });
771
794
  return;
772
795
  }
773
796
  const arriving = acceptedMessage(event.message);
774
797
  if (arriving === null)
775
798
  return;
776
- alive.heardAt(conversation, arriving.position);
799
+ alive.heardAt(conversation, arriving.change);
777
800
  if (alive.held.now().replicated.has(conversation)) {
778
801
  if (alive.deps.full()) {
779
802
  alive.gapOpened(conversation);
@@ -791,19 +814,27 @@ function heard(alive, event) {
791
814
  emit(alive, conversation);
792
815
  }
793
816
  async function kept(alive, local, conversation, following) {
794
- const shown = following === null || empty(following) ? null : following;
795
- if (shown === null)
817
+ const draft = following === null || empty(following) ? null : following;
818
+ if (draft === null)
796
819
  await local.dropChatDraft(conversation);
797
820
  else
798
- await local.keepChatDrafts([shown]);
799
- shownDraft(alive, conversation, shown);
821
+ await local.keepChatDrafts([draft]);
822
+ heldInViews(alive, conversation, { draft });
800
823
  }
801
- function shownDraft(alive, conversation, draft) {
824
+ function heldInViews(alive, conversation, held) {
802
825
  for (const view of alive.views()) {
803
826
  if (view.conversation === conversation)
804
- view.held.set({ draft });
827
+ view.held.set(held);
805
828
  }
806
829
  }
830
+ async function factOf(local, name) {
831
+ return Object.fromEntries((await local.conversationFacts())
832
+ .filter((fact) => fact.name === name)
833
+ .map(({ conversation, value }) => [conversation, value]));
834
+ }
835
+ async function dismissedIn(local, conversation) {
836
+ return (await factOf(local, 'dismissedSuggestion'))[conversation] ?? null;
837
+ }
807
838
  async function draftAt(local, conversation) {
808
839
  return (await local.chatDraft(conversation)) ?? undefined;
809
840
  }
@@ -834,9 +865,9 @@ function moved(alive, agent, operation, to) {
834
865
  : [{ ...unsent, conversation: to }];
835
866
  const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
836
867
  await local.keepChatDrafts([left, ...taken]);
837
- shownDraft(alive, from, left);
868
+ heldInViews(alive, from, { draft: left });
838
869
  for (const one of taken)
839
- shownDraft(alive, to, one);
870
+ heldInViews(alive, to, { draft: one });
840
871
  alive.deps.publish(conversationChangeSet([from, to]));
841
872
  });
842
873
  }
@@ -867,13 +898,10 @@ async function submitted(alive, conversation, operation, draft) {
867
898
  });
868
899
  return submission;
869
900
  }
870
- async function dispatched(alive, conversation, submission) {
901
+ async function dispatched(alive, conversation, submission, resent = false) {
871
902
  const dispatch = alive.sendingBegan(submission.operation);
872
903
  try {
873
- const result = await invoke(alive, 'conversations/send', conversation, {
874
- operation_id: submission.operation,
875
- text: submission.text,
876
- });
904
+ const result = await invoke(alive, 'conversations/send', conversation, submission.operation, { text: submission.text }, { step: 'sending', instance: `message ${submission.operation}` });
877
905
  dispatch.set({ answered: result.kind !== 'transport-uncertainty' });
878
906
  if (result.kind === 'revoked-authorization')
879
907
  return result;
@@ -881,6 +909,7 @@ async function dispatched(alive, conversation, submission) {
881
909
  alive.deps.unanswered();
882
910
  return result;
883
911
  }
912
+ alive.deps.heard(submission.operation, resent);
884
913
  const turn = answered(result, 'message_id');
885
914
  await withDraft(alive, conversation, async (current, local) => {
886
915
  const held = current?.submitted?.find((one) => one.operation === submission.operation);
@@ -900,8 +929,8 @@ async function dispatched(alive, conversation, submission) {
900
929
  alive.sendingEnded(submission.operation, dispatch);
901
930
  }
902
931
  }
903
- function invoke(alive, name, conversation, extra = { operation_id: changeBatchIdentity() }) {
904
- return alive.deps.call(name, { conversation_id: conversation, ...extra });
932
+ function invoke(alive, name, conversation, operation, extra = {}, attempt) {
933
+ return alive.deps.call(name, { conversation_id: conversation, operation_id: operation, ...extra }, attempt);
905
934
  }
906
935
  export function conversationSurface(alive) {
907
936
  return {
@@ -922,7 +951,12 @@ export function conversationSurface(alive) {
922
951
  return alive.deps.durably(async (local) => {
923
952
  const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before, phrase);
924
953
  const total = asked.total ? await local.messageCount(conversation, phrase) : null;
925
- return structuredClone(viewOf(alive, conversation, messages, frameOf(asked, messages, more, total), (await local.chatDraft(conversation)) ?? null, await endedIn(alive, conversation)));
954
+ return structuredClone(viewOf(alive, conversation, messages, frameOf(asked, messages, more, total), {
955
+ draft: (await local.chatDraft(conversation)) ?? null,
956
+ ended: await endedIn(alive, conversation),
957
+ dismissed: await dismissedIn(local, conversation),
958
+ pins: (await local.facts()).chatPins,
959
+ }));
926
960
  });
927
961
  },
928
962
  async create(input) {
@@ -930,7 +964,7 @@ export function conversationSurface(alive) {
930
964
  const conversation = answered(result, 'conversation_id');
931
965
  if (conversation === null)
932
966
  return result;
933
- const listed = (await heldList(alive)) ?? [];
967
+ const listed = (await heldListing(alive))?.conversations ?? [];
934
968
  if (!listed.some((entry) => entry.conversation === conversation)) {
935
969
  alive.creating(conversation);
936
970
  }
@@ -959,40 +993,90 @@ export function conversationSurface(alive) {
959
993
  }
960
994
  return OFFLINE;
961
995
  },
962
- answer(conversation, answer) {
963
- return invoke(alive, 'conversations/interaction/answer', conversation, {
964
- operation_id: changeBatchIdentity(),
965
- ...answer,
996
+ answer(conversation, answer, operation) {
997
+ return invoke(alive, 'conversations/interaction/answer', conversation, operation, answer);
998
+ },
999
+ interrupt(conversation, operation) {
1000
+ return invoke(alive, 'conversations/interrupt', conversation, operation);
1001
+ },
1002
+ kill(conversation, operation) {
1003
+ return invoke(alive, 'conversations/kill', conversation, operation);
1004
+ },
1005
+ delete(conversation, operation) {
1006
+ return invoke(alive, 'conversations/delete', conversation, operation);
1007
+ },
1008
+ takeOut(conversation, message, operation) {
1009
+ return invoke(alive, 'conversations/take-out', conversation, operation, {
1010
+ message_id: message,
1011
+ });
1012
+ },
1013
+ decide({ request, room, message }, decision, operation) {
1014
+ return alive.deps.call('agent-functions/decide', {
1015
+ room_ref: room,
1016
+ request,
1017
+ ...(message === undefined ? {} : { message }),
1018
+ decision,
1019
+ operation_id: operation,
1020
+ });
1021
+ },
1022
+ rename(conversation, name, operation) {
1023
+ return invoke(alive, 'conversations/rename', conversation, operation, { name });
1024
+ },
1025
+ option(conversation, option, value, operation) {
1026
+ return invoke(alive, 'conversations/option/set', conversation, operation, { option, value });
1027
+ },
1028
+ retry(conversation, message, operation) {
1029
+ return invoke(alive, 'conversations/retry', conversation, operation, {
1030
+ message_id: message,
966
1031
  });
967
1032
  },
968
- interrupt(conversation) {
969
- return invoke(alive, 'conversations/interrupt', conversation);
1033
+ async pin(figures) {
1034
+ const pins = [...figures];
1035
+ await alive.deps.durably((local) => local.fact('chatPins', pins));
1036
+ const views = [...alive.views()];
1037
+ for (const view of views)
1038
+ view.held.set({ pins });
1039
+ for (const conversation of new Set(views.map((view) => view.conversation))) {
1040
+ emit(alive, conversation);
1041
+ }
970
1042
  },
971
- kill(conversation) {
972
- return invoke(alive, 'conversations/kill', conversation);
1043
+ async openTurn(conversation, turn) {
1044
+ await alive.deps.durably((local) => local.keepConversationFact({ conversation, name: 'openedTurn', value: turn }));
1045
+ tellList(alive);
973
1046
  },
974
- delete(conversation) {
975
- return invoke(alive, 'conversations/delete', conversation);
1047
+ dismissSuggestion(conversation, suggestion) {
1048
+ return alive.deps.durably(async (local) => {
1049
+ if ((await dismissedIn(local, conversation)) === suggestion)
1050
+ return;
1051
+ await local.keepConversationFact({
1052
+ conversation,
1053
+ name: 'dismissedSuggestion',
1054
+ value: suggestion,
1055
+ });
1056
+ heldInViews(alive, conversation, { dismissed: suggestion });
1057
+ alive.deps.publish(conversationChangeSet([conversation]));
1058
+ });
976
1059
  },
977
1060
  };
978
1061
  }
1062
+ function rewritten(alive, conversation, text, revision = chatDraftRevision()) {
1063
+ return withDraft(alive, conversation, (current) => ({
1064
+ conversation,
1065
+ text: text(current?.text ?? ''),
1066
+ revision,
1067
+ ...(current === undefined ? {} : pending(current)),
1068
+ }));
1069
+ }
979
1070
  export function chatDraftSurface(alive) {
980
1071
  return {
981
- write(conversation, draft, revision = chatDraftRevision()) {
982
- return withDraft(alive, conversation, (current) => ({
983
- conversation,
984
- text: draft.text,
985
- revision,
986
- ...(current === undefined ? {} : pending(current)),
987
- }));
1072
+ write(conversation, draft, revision) {
1073
+ return rewritten(alive, conversation, () => draft.text, revision);
988
1074
  },
989
1075
  prepend(conversation, draft) {
990
- return withDraft(alive, conversation, (current) => ({
991
- conversation,
992
- ...(current === undefined ? { text: draft.text } : prepended(current, draft)),
993
- revision: chatDraftRevision(),
994
- ...(current === undefined ? {} : pending(current)),
995
- }));
1076
+ return rewritten(alive, conversation, (held) => joinedDraft(draft.text, held));
1077
+ },
1078
+ append(conversation, draft) {
1079
+ return rewritten(alive, conversation, (held) => joinedDraft(held, draft.text));
996
1080
  },
997
1081
  discard(conversation) {
998
1082
  return withDraft(alive, conversation, (current) => current === undefined
@@ -1002,31 +1086,34 @@ export function chatDraftSurface(alive) {
1002
1086
  };
1003
1087
  }
1004
1088
  export async function conversationsOpened(alive, local) {
1005
- const { conversationList, conversationCursor } = await local.facts();
1006
- if (conversationList === null || alive.known())
1089
+ const { conversationList, agentList, conversationCursor } = await local.facts();
1090
+ if (conversationList === null || agentList === null || alive.known())
1007
1091
  return;
1008
1092
  alive.deps.stream()?.hold(conversationCursor);
1009
1093
  conversationsLearned(alive);
1010
1094
  }
1095
+ export function reconcileOpening(alive) {
1096
+ alive.held.set({ openingRead: true });
1097
+ return reconcileConversations(alive);
1098
+ }
1011
1099
  export async function reconcileConversations(alive) {
1012
1100
  if (!alive.known())
1013
1101
  return;
1014
1102
  const local = await alive.deps.local();
1015
- const [held, kept, { conversationList }] = await Promise.all([
1103
+ const [held, stored, { conversationList, agentList }] = await Promise.all([
1016
1104
  local.conversations(),
1017
- local.chatDrafts(),
1105
+ local.storedConversations(),
1018
1106
  local.facts(),
1019
1107
  ]);
1020
1108
  const list = conversationList ?? [];
1109
+ const listing = { conversations: list, agents: agentList ?? [] };
1021
1110
  const entries = new Map(list.map((entry) => [entry.conversation, entry]));
1022
1111
  const named = new Set(entries.keys());
1023
1112
  const reads = new Map(held.map((one) => [one.conversation, one]));
1024
1113
  const { replicated } = alive.held.now();
1025
- const leaving = [
1026
- ...new Set([...replicated, ...reads.keys(), ...kept.map(({ conversation }) => conversation)]),
1027
- ].filter((conversation) => !named.has(conversation) &&
1114
+ const leaving = [...new Set([...replicated, ...stored])].filter((conversation) => !named.has(conversation) &&
1028
1115
  !alive.isCreating(conversation) &&
1029
- (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation, list)));
1116
+ (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation, listing)));
1030
1117
  for (const conversation of named) {
1031
1118
  if (!replicated.has(conversation) &&
1032
1119
  missed(alive, conversation, reads.get(conversation), entries.get(conversation))) {
@@ -1061,6 +1148,17 @@ export async function catchUpConversations(alive, progress) {
1061
1148
  if (!alive.current())
1062
1149
  progress.complete = false;
1063
1150
  }
1151
+ export async function holdsSubmission(alive, operation) {
1152
+ if (alive.sending(operation))
1153
+ return true;
1154
+ const local = await alive.deps.local();
1155
+ for (const { conversation } of await local.chatDrafts()) {
1156
+ const held = (await local.chatDraft(conversation))?.submitted;
1157
+ if (held?.some((one) => one.operation === operation && one.turn === null) === true)
1158
+ return true;
1159
+ }
1160
+ return false;
1161
+ }
1064
1162
  export async function resendConversations(alive, progress) {
1065
1163
  const local = await alive.deps.local();
1066
1164
  const tried = new Set();
@@ -1072,7 +1170,7 @@ export async function resendConversations(alive, progress) {
1072
1170
  if (alive.deps.closed())
1073
1171
  return;
1074
1172
  tried.add(submission.operation);
1075
- const result = await dispatched(alive, conversation, submission);
1173
+ const result = await dispatched(alive, conversation, submission, true);
1076
1174
  if (result.kind === 'revoked-authorization')
1077
1175
  return;
1078
1176
  if (result.kind === 'transport-uncertainty') {
@@ -1092,22 +1190,25 @@ export function watchConversation(alive, conversation, options, deliver) {
1092
1190
  const { view, watcher } = alive.viewJoined(key, conversation, asked, deliver);
1093
1191
  const { held } = view;
1094
1192
  const reading = new AbortController();
1095
- void untilAnswered(() => alive.deps.durably(async (local) => {
1193
+ void untilAnswered(retrying(alive.deps.retries, 'reading', `window ${key}`, () => alive.deps.durably(async (local) => {
1096
1194
  if (held.now().window !== null)
1097
1195
  return;
1098
1196
  const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
1099
1197
  held.set({ draft: (await local.chatDraft(conversation)) ?? null });
1100
1198
  held.set({ ended: await endedIn(alive, conversation) });
1199
+ held.set({ dismissed: await dismissedIn(local, conversation) });
1200
+ held.set({ pins: (await local.facts()).chatPins });
1101
1201
  held.set({ window: messages });
1102
1202
  held.set({ more });
1103
1203
  held.set({ total: asked.total ? await local.messageCount(conversation) : null });
1104
- }), reading.signal)
1204
+ })), reading.signal)
1105
1205
  .then(() => {
1106
1206
  if (alive.deps.closed() || !view.admit(watcher))
1107
1207
  return;
1108
- const { window, draft, ended } = held.now();
1109
- const shown = window ?? [];
1110
- watcher.notify(viewOf(alive, conversation, shown, sharedFrame(view, shown), draft, ended));
1208
+ const now = held.now();
1209
+ const shown = now.window ?? [];
1210
+ const frame = sharedFrame(view, shown);
1211
+ watcher.notify(viewOf(alive, conversation, shown, frame, now));
1111
1212
  })
1112
1213
  .catch(() => { });
1113
1214
  return () => {
@@ -1123,9 +1224,13 @@ export function watchConversationList(alive, deliver) {
1123
1224
  export function watchConversationAgents(alive, deliver) {
1124
1225
  alive.agentListeners.join(deliver);
1125
1226
  conversationsLearned(alive);
1126
- readAgents(alive);
1127
1227
  return () => alive.agentListeners.leave(deliver);
1128
1228
  }
1229
+ export function watchListedAgents(alive, deliver) {
1230
+ alive.listedListeners.join(deliver);
1231
+ conversationsLearned(alive);
1232
+ return () => alive.listedListeners.leave(deliver);
1233
+ }
1129
1234
  export function conversationsChanged(alive, set) {
1130
1235
  for (const conversation of set.conversations)
1131
1236
  emit(alive, conversation);
@@ -1137,26 +1242,32 @@ export function receiveConversationEvent(alive, event) {
1137
1242
  if (event.whole)
1138
1243
  alive.relistBegun();
1139
1244
  if (alive.relisting())
1140
- alive.relistHeard(event.entries);
1245
+ alive.relistHeard(event.entries, event.agents);
1141
1246
  if (event.more && alive.relisting())
1142
1247
  return;
1143
1248
  const whole = alive.relistTaken();
1144
1249
  listChanged(alive, {
1145
1250
  replacing: whole !== null,
1146
- set: whole === null ? event.entries : [...whole.values()],
1251
+ set: whole === null ? event.entries : [...whole.conversations.values()],
1252
+ agents: whole === null ? event.agents : [...whole.agents.values()],
1147
1253
  gone: null,
1148
1254
  ...(event.more ? { paged: true } : { cursor: event.cursor }),
1149
1255
  });
1150
1256
  return;
1151
1257
  }
1152
1258
  if (event.kind === 'listed') {
1153
- alive.relistHeard([event.entry]);
1154
- listChanged(alive, { replacing: false, set: [event.entry], gone: null });
1259
+ alive.relistHeard([event.entry], []);
1260
+ listChanged(alive, { replacing: false, set: [event.entry], agents: [], gone: null });
1261
+ return;
1262
+ }
1263
+ if (event.kind === 'agent') {
1264
+ alive.relistHeard([], [event.agent]);
1265
+ listChanged(alive, { replacing: false, set: [], agents: [event.agent], gone: null });
1155
1266
  return;
1156
1267
  }
1157
1268
  if (event.kind === 'unlisted') {
1158
1269
  alive.relistDropped(event.conversation);
1159
- listChanged(alive, { replacing: false, set: [], gone: event.conversation });
1270
+ listChanged(alive, { replacing: false, set: [], agents: [], gone: event.conversation });
1160
1271
  return;
1161
1272
  }
1162
1273
  heard(alive, event);