@agentshouse/mdruntime 0.1.0-cd26b960b7262 → 0.1.0-cd30853a1b15e

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