@agentshouse/mdruntime 0.1.0-cf96c797c5fa8 → 0.1.0-cf9a84afc6def

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 (234) hide show
  1. package/README.md +18 -15
  2. package/dist/adapter.d.ts +117 -49
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/attached-model.d.ts +5 -1
  5. package/dist/attached-model.d.ts.map +1 -1
  6. package/dist/attached-model.js +38 -4
  7. package/dist/attempt.d.ts.map +1 -1
  8. package/dist/attempt.js +14 -1
  9. package/dist/binary.d.ts +0 -5
  10. package/dist/binary.d.ts.map +1 -1
  11. package/dist/binary.js +1 -1
  12. package/dist/bootstrap.d.ts +27 -11
  13. package/dist/bootstrap.d.ts.map +1 -1
  14. package/dist/bootstrap.js +184 -81
  15. package/dist/chain.d.ts +11 -0
  16. package/dist/chain.d.ts.map +1 -0
  17. package/dist/chain.js +64 -0
  18. package/dist/change-set.d.ts +9 -14
  19. package/dist/change-set.d.ts.map +1 -1
  20. package/dist/change-set.js +11 -63
  21. package/dist/change.d.ts +31 -1
  22. package/dist/change.d.ts.map +1 -1
  23. package/dist/change.js +10 -1
  24. package/dist/client.d.ts +26 -15
  25. package/dist/client.d.ts.map +1 -1
  26. package/dist/client.js +98 -12
  27. package/dist/collection.d.ts +10 -7
  28. package/dist/collection.d.ts.map +1 -1
  29. package/dist/collection.js +38 -79
  30. package/dist/compile.d.ts +16 -0
  31. package/dist/compile.d.ts.map +1 -0
  32. package/dist/compile.js +786 -0
  33. package/dist/connection.d.ts +44 -16
  34. package/dist/connection.d.ts.map +1 -1
  35. package/dist/connection.js +3561 -965
  36. package/dist/conversation.d.ts +36 -16
  37. package/dist/conversation.d.ts.map +1 -1
  38. package/dist/conversation.js +659 -254
  39. package/dist/digest.d.ts +7 -0
  40. package/dist/digest.d.ts.map +1 -0
  41. package/dist/digest.js +113 -0
  42. package/dist/durability.d.ts +4 -1
  43. package/dist/durability.d.ts.map +1 -1
  44. package/dist/durability.js +5 -0
  45. package/dist/entry.d.ts +20 -0
  46. package/dist/entry.d.ts.map +1 -0
  47. package/dist/entry.js +151 -0
  48. package/dist/fence.d.ts +6 -0
  49. package/dist/fence.d.ts.map +1 -0
  50. package/dist/fence.js +10 -0
  51. package/dist/file.d.ts +2 -19
  52. package/dist/file.d.ts.map +1 -1
  53. package/dist/file.js +7 -122
  54. package/dist/fulltext.d.ts +27 -0
  55. package/dist/fulltext.d.ts.map +1 -0
  56. package/dist/fulltext.js +370 -0
  57. package/dist/host.d.ts +13 -3
  58. package/dist/host.d.ts.map +1 -1
  59. package/dist/host.js +128 -19
  60. package/dist/identity.d.ts +1 -0
  61. package/dist/identity.d.ts.map +1 -1
  62. package/dist/identity.js +3 -0
  63. package/dist/index.d.ts +16 -10
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +6 -0
  66. package/dist/live.d.ts +22 -0
  67. package/dist/live.d.ts.map +1 -0
  68. package/dist/live.js +123 -0
  69. package/dist/media.d.ts +8 -0
  70. package/dist/media.d.ts.map +1 -0
  71. package/dist/media.js +218 -0
  72. package/dist/memory.d.ts +14 -5
  73. package/dist/memory.d.ts.map +1 -1
  74. package/dist/memory.js +204 -92
  75. package/dist/messages.d.ts +28 -0
  76. package/dist/messages.d.ts.map +1 -0
  77. package/dist/messages.js +90 -0
  78. package/dist/model.d.ts +9 -2
  79. package/dist/model.d.ts.map +1 -1
  80. package/dist/model.js +8 -2
  81. package/dist/name.d.ts +4 -0
  82. package/dist/name.d.ts.map +1 -0
  83. package/dist/name.js +122 -0
  84. package/dist/observation.d.ts +20 -2
  85. package/dist/observation.d.ts.map +1 -1
  86. package/dist/observation.js +113 -12
  87. package/dist/opened.d.ts +22 -0
  88. package/dist/opened.d.ts.map +1 -0
  89. package/dist/opened.js +107 -0
  90. package/dist/outcome.d.ts +49 -7
  91. package/dist/outcome.d.ts.map +1 -1
  92. package/dist/outcome.js +40 -0
  93. package/dist/overlay.d.ts +3 -4
  94. package/dist/overlay.d.ts.map +1 -1
  95. package/dist/overlay.js +100 -182
  96. package/dist/paths.d.ts +1 -0
  97. package/dist/paths.d.ts.map +1 -1
  98. package/dist/paths.js +4 -0
  99. package/dist/query.d.ts +66 -4
  100. package/dist/query.d.ts.map +1 -1
  101. package/dist/query.js +48 -7
  102. package/dist/reach.d.ts +18 -6
  103. package/dist/reach.d.ts.map +1 -1
  104. package/dist/reach.js +221 -74
  105. package/dist/react.d.ts +8 -6
  106. package/dist/react.d.ts.map +1 -1
  107. package/dist/react.js +7 -17
  108. package/dist/readiness.d.ts +52 -0
  109. package/dist/readiness.d.ts.map +1 -0
  110. package/dist/readiness.js +27 -0
  111. package/dist/recovery.d.ts +26 -0
  112. package/dist/recovery.d.ts.map +1 -0
  113. package/dist/recovery.js +224 -0
  114. package/dist/refresh.d.ts +16 -14
  115. package/dist/refresh.d.ts.map +1 -1
  116. package/dist/refresh.js +115 -51
  117. package/dist/replica.d.ts +55 -33
  118. package/dist/replica.d.ts.map +1 -1
  119. package/dist/replica.js +297 -154
  120. package/dist/room-draft.d.ts +34 -4
  121. package/dist/room-draft.d.ts.map +1 -1
  122. package/dist/room-draft.js +105 -31
  123. package/dist/rows.d.ts +37 -37
  124. package/dist/rows.d.ts.map +1 -1
  125. package/dist/rows.js +173 -104
  126. package/dist/save.d.ts +52 -9
  127. package/dist/save.d.ts.map +1 -1
  128. package/dist/save.js +539 -171
  129. package/dist/search.d.ts +39 -28
  130. package/dist/search.d.ts.map +1 -1
  131. package/dist/search.js +37 -105
  132. package/dist/seed.d.ts +16 -0
  133. package/dist/seed.d.ts.map +1 -0
  134. package/dist/seed.js +75 -0
  135. package/dist/shared.d.ts +15 -0
  136. package/dist/shared.d.ts.map +1 -0
  137. package/dist/shared.js +421 -0
  138. package/dist/status.d.ts +5 -0
  139. package/dist/status.d.ts.map +1 -1
  140. package/dist/status.js +3 -1
  141. package/dist/store.d.ts +102 -76
  142. package/dist/store.d.ts.map +1 -1
  143. package/dist/store.js +313 -443
  144. package/dist/stored.d.ts +130 -0
  145. package/dist/stored.d.ts.map +1 -0
  146. package/dist/stored.js +560 -0
  147. package/dist/tables.d.ts +325 -0
  148. package/dist/tables.d.ts.map +1 -0
  149. package/dist/tables.js +578 -0
  150. package/dist/transfer.d.ts +0 -5
  151. package/dist/transfer.d.ts.map +1 -1
  152. package/dist/turn.d.ts +9 -0
  153. package/dist/turn.d.ts.map +1 -0
  154. package/dist/turn.js +75 -0
  155. package/dist/validation.d.ts +29 -0
  156. package/dist/validation.d.ts.map +1 -0
  157. package/dist/validation.js +101 -0
  158. package/dist/windows.d.ts +22 -0
  159. package/dist/windows.d.ts.map +1 -0
  160. package/dist/windows.js +89 -0
  161. package/dist/wire.d.ts +164 -32
  162. package/dist/wire.d.ts.map +1 -1
  163. package/dist/wire.js +55 -31
  164. package/dist/words.d.ts +19 -0
  165. package/dist/words.d.ts.map +1 -0
  166. package/dist/words.js +156 -0
  167. package/package.json +27 -24
  168. package/AGENTS.md +0 -318
  169. package/dist/chat/acts.d.ts +0 -34
  170. package/dist/chat/acts.d.ts.map +0 -1
  171. package/dist/chat/acts.js +0 -36
  172. package/dist/chat/context.d.ts +0 -3
  173. package/dist/chat/context.d.ts.map +0 -1
  174. package/dist/chat/context.js +0 -10
  175. package/dist/chat/input.d.ts +0 -9
  176. package/dist/chat/input.d.ts.map +0 -1
  177. package/dist/chat/input.js +0 -99
  178. package/dist/chat/kit.d.ts +0 -60
  179. package/dist/chat/kit.d.ts.map +0 -1
  180. package/dist/chat/kit.js +0 -123
  181. package/dist/chat/lasting.d.ts +0 -6
  182. package/dist/chat/lasting.d.ts.map +0 -1
  183. package/dist/chat/lasting.js +0 -19
  184. package/dist/chat/list.d.ts +0 -20
  185. package/dist/chat/list.d.ts.map +0 -1
  186. package/dist/chat/list.js +0 -59
  187. package/dist/chat/markdown.d.ts +0 -19
  188. package/dist/chat/markdown.d.ts.map +0 -1
  189. package/dist/chat/markdown.js +0 -187
  190. package/dist/chat/message.d.ts +0 -6
  191. package/dist/chat/message.d.ts.map +0 -1
  192. package/dist/chat/message.js +0 -192
  193. package/dist/chat/messages.d.ts +0 -21
  194. package/dist/chat/messages.d.ts.map +0 -1
  195. package/dist/chat/messages.js +0 -151
  196. package/dist/chat/name.d.ts +0 -6
  197. package/dist/chat/name.d.ts.map +0 -1
  198. package/dist/chat/name.js +0 -37
  199. package/dist/chat/opened.d.ts +0 -11
  200. package/dist/chat/opened.d.ts.map +0 -1
  201. package/dist/chat/opened.js +0 -17
  202. package/dist/chat/overlay.d.ts +0 -31
  203. package/dist/chat/overlay.d.ts.map +0 -1
  204. package/dist/chat/overlay.js +0 -97
  205. package/dist/chat/parts.d.ts +0 -57
  206. package/dist/chat/parts.d.ts.map +0 -1
  207. package/dist/chat/parts.js +0 -119
  208. package/dist/chat/process.d.ts +0 -20
  209. package/dist/chat/process.d.ts.map +0 -1
  210. package/dist/chat/process.js +0 -32
  211. package/dist/chat/provider.d.ts +0 -63
  212. package/dist/chat/provider.d.ts.map +0 -1
  213. package/dist/chat/provider.js +0 -645
  214. package/dist/chat/question.d.ts +0 -9
  215. package/dist/chat/question.d.ts.map +0 -1
  216. package/dist/chat/question.js +0 -80
  217. package/dist/chat/questions.d.ts +0 -31
  218. package/dist/chat/questions.d.ts.map +0 -1
  219. package/dist/chat/questions.js +0 -111
  220. package/dist/chat/read.d.ts +0 -5
  221. package/dist/chat/read.d.ts.map +0 -1
  222. package/dist/chat/read.js +0 -13
  223. package/dist/chat/ready.d.ts +0 -33
  224. package/dist/chat/ready.d.ts.map +0 -1
  225. package/dist/chat/ready.js +0 -91
  226. package/dist/chat/style.d.ts +0 -4
  227. package/dist/chat/style.d.ts.map +0 -1
  228. package/dist/chat/style.js +0 -302
  229. package/dist/chat.d.ts +0 -14
  230. package/dist/chat.d.ts.map +0 -1
  231. package/dist/chat.js +0 -8
  232. package/dist/read-side.d.ts +0 -18
  233. package/dist/read-side.d.ts.map +0 -1
  234. package/dist/read-side.js +0 -440
@@ -1,14 +1,29 @@
1
1
  import { conversationChangeSet } from './change-set.js';
2
2
  import { runtimeError } from './failure.js';
3
- import { declaredFile } from './file.js';
4
- import { chatDraftRevision } from './identity.js';
3
+ import { refusedForSpace } from './durability.js';
4
+ import { changeBatchIdentity, chatDraftRevision } from './identity.js';
5
+ import { OFFLINE } from './outcome.js';
6
+ import { untilAnswered } from './recovery.js';
7
+ import { capped, cursorOf, decodedCursor, FROM_START, messageOrder, precedes } from './windows.js';
5
8
  const QUIET = {
6
9
  turn: null,
7
10
  questions: [],
8
11
  process: null,
9
12
  commands: [],
10
13
  options: [],
14
+ starting: null,
15
+ link: null,
16
+ deliveryUnknown: [],
11
17
  };
18
+ const FACTS = [
19
+ 'questions',
20
+ 'process',
21
+ 'commands',
22
+ 'options',
23
+ 'starting',
24
+ 'link',
25
+ 'deliveryUnknown',
26
+ ];
12
27
  export function unheldView(conversation) {
13
28
  return {
14
29
  conversation,
@@ -18,9 +33,26 @@ export function unheldView(conversation) {
18
33
  process: null,
19
34
  commands: [],
20
35
  options: [],
36
+ starting: null,
37
+ link: null,
38
+ deliveryUnknown: [],
21
39
  chatDraft: null,
22
40
  };
23
41
  }
42
+ export const SESSION_STARTED = 'Session started';
43
+ const NEW_CHAT = 'new:';
44
+ export const HISTORY_CONVERSATIONS = 64;
45
+ export function newChat(agent) {
46
+ return `${NEW_CHAT}${agent}`;
47
+ }
48
+ function creationOf(input) {
49
+ if (typeof input !== 'object' || input === null)
50
+ return null;
51
+ const { agent_id: agent, operation_id: operation, replaces, } = input;
52
+ return typeof agent === 'string' && typeof operation === 'string' && replaces === undefined
53
+ ? { agent, operation }
54
+ : null;
55
+ }
24
56
  function startedTurn(turn, operation) {
25
57
  if (operation.op === 'begin')
26
58
  return { turn, started: operation.at, parts: [], context: null };
@@ -70,18 +102,28 @@ function appliedTurn(held, operation) {
70
102
  parts: held.parts.map((kept, at) => (at === operation.index ? changed : kept)),
71
103
  };
72
104
  }
73
- function ordered(left, right) {
74
- if (left.position !== right.position)
75
- return left.position < right.position ? -1 : 1;
76
- if (left.identity !== right.identity)
77
- return left.identity < right.identity ? -1 : 1;
78
- return 0;
79
- }
80
- function merged(held, arriving) {
105
+ function windowOf(held, arriving, limit) {
81
106
  const byIdentity = new Map(held.map((message) => [message.identity, message]));
82
107
  for (const message of arriving)
83
108
  byIdentity.set(message.identity, message);
84
- return [...byIdentity.values()].sort(ordered);
109
+ const next = [...byIdentity.values()].sort(messageOrder);
110
+ const start = Math.max(0, next.length - limit);
111
+ return { messages: next.slice(start), dropped: start > 0 };
112
+ }
113
+ function frameOf(asked, messages, more, total) {
114
+ const oldest = messages[0];
115
+ const next = !more
116
+ ? undefined
117
+ : oldest === undefined
118
+ ? (asked.after ?? FROM_START)
119
+ : cursorOf([oldest.position, oldest.identity]);
120
+ return {
121
+ ...(next === undefined ? {} : { next }),
122
+ ...(total === null ? {} : { total }),
123
+ };
124
+ }
125
+ function newestOf(arriving, from) {
126
+ return arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from);
85
127
  }
86
128
  function acceptedMessage(value) {
87
129
  if (typeof value !== 'object' || value === null)
@@ -140,88 +182,160 @@ function historyPages(result) {
140
182
  }
141
183
  return pages;
142
184
  }
143
- function turnOf(result) {
185
+ function answered(result, field) {
144
186
  if (result.kind !== 'action')
145
187
  return null;
146
188
  const answer = result.result;
147
189
  if (typeof answer !== 'object' || answer === null)
148
190
  return null;
149
- const record = answer;
150
- return typeof record.turn === 'string' ? record.turn : null;
191
+ const value = answer[field];
192
+ return typeof value === 'string' ? value : null;
151
193
  }
152
- function limitOf(options) {
153
- const { limit } = options;
154
- if (limit === undefined)
155
- return undefined;
156
- if (!Number.isInteger(limit) || limit < 0) {
194
+ function beforeOf(after) {
195
+ if (after === undefined)
196
+ return null;
197
+ const decoded = typeof after === 'string' ? decodedCursor(after) : null;
198
+ if (decoded?.length === 0)
199
+ return null;
200
+ const [position, identity] = decoded ?? [];
201
+ if (decoded?.length !== 2 || typeof position !== 'string' || typeof identity !== 'string') {
202
+ throw runtimeError('invalid-after', `conversation after must be the next an earlier view returned, got ${String(after)}`);
203
+ }
204
+ return [position, identity];
205
+ }
206
+ function askedOf(options) {
207
+ const { limit, after, total } = options;
208
+ if (limit !== undefined && (!Number.isInteger(limit) || limit < 0)) {
157
209
  throw runtimeError('invalid-limit', `conversation limit must be a non-negative integer, got ${String(limit)}`);
158
210
  }
159
- return limit;
211
+ return { limit: capped(limit), after, before: beforeOf(after), total: total === true };
212
+ }
213
+ const EXPIRED = { kind: 'rejected', code: 'expired', fields: {}, retryable: false };
214
+ const DURABLE_WRITE = {
215
+ kind: 'rejected',
216
+ code: 'durable-write',
217
+ fields: {},
218
+ retryable: true,
219
+ };
220
+ function unwritten(draft) {
221
+ return draft.text === '';
222
+ }
223
+ function pending(draft) {
224
+ return {
225
+ ...(draft.submitted === undefined ? {} : { submitted: draft.submitted }),
226
+ ...(draft.moves === undefined ? {} : { moves: draft.moves }),
227
+ };
228
+ }
229
+ function empty(draft) {
230
+ return unwritten(draft) && draft.submitted === undefined && draft.moves === undefined;
231
+ }
232
+ function withSubmitted(draft, submitted) {
233
+ const { submitted: _held, ...unsent } = draft;
234
+ return submitted.length === 0 ? unsent : { ...unsent, submitted };
235
+ }
236
+ function prepended(draft, said) {
237
+ return { text: [said.text, draft.text].filter((one) => one !== '').join('\n') };
238
+ }
239
+ function returned(draft, submission, others) {
240
+ return withSubmitted({
241
+ conversation: draft.conversation,
242
+ ...prepended(draft, submission),
243
+ revision: chatDraftRevision(),
244
+ }, others);
160
245
  }
161
246
  export function createConversationStore(deps) {
162
- const messages = new Map();
247
+ const stored = new Set();
163
248
  const reads = new Map();
249
+ const partials = new Set();
164
250
  const drafts = new Map();
165
251
  const sending = new Map();
166
252
  const behind = new Set();
253
+ const catching = new Map();
167
254
  const gaps = new Map();
168
255
  const lives = new Map();
256
+ const unsent = new Map();
169
257
  const views = new Map();
170
258
  const listing = new Map();
259
+ const creations = new Set();
171
260
  const listWatchers = new Set();
261
+ const unlisted = new Set();
172
262
  let listKnown = false;
263
+ let listHeld = false;
264
+ let listKept = false;
173
265
  const agentWatchers = new Set();
174
- let agentNames = new Map();
175
- let retiredAgents = new Set();
176
- let agentsRead = null;
266
+ const unnamed = new Set();
267
+ let agentsShown = null;
268
+ let listedAgents = null;
269
+ let agentsReading = false;
177
270
  let replicated = new Set();
178
271
  let handedReplicated;
179
272
  function liveOf(conversation) {
180
273
  return lives.get(conversation) ?? QUIET;
181
274
  }
182
- function shownMessages(conversation, limit) {
183
- const held = messages.get(conversation) ?? [];
184
- const shown = limit === undefined ? held : held.slice(Math.max(0, held.length - limit));
185
- return shown.map(({ identity, turn, text, message }) => ({
186
- identity,
187
- turn,
188
- text,
189
- message: structuredClone(message),
190
- }));
191
- }
192
- function viewOf(conversation, limit, shown = shownMessages(conversation, limit)) {
275
+ function viewOf(conversation, held, frame) {
193
276
  const draft = drafts.get(conversation);
194
277
  const facts = liveOf(conversation);
195
278
  return {
196
279
  conversation,
197
- messages: shown,
280
+ messages: held.map(({ identity, turn, text, message }) => ({
281
+ identity,
282
+ turn,
283
+ text,
284
+ message,
285
+ })),
286
+ ...frame,
198
287
  turn: structuredClone(facts.turn),
199
288
  questions: structuredClone(facts.questions),
200
289
  process: facts.process === null ? null : { ...facts.process },
201
290
  commands: structuredClone(facts.commands),
202
291
  options: structuredClone(facts.options),
203
- chatDraft: draft === undefined || draft.turn !== null || sending.get(conversation)?.draft === draft
292
+ starting: facts.starting,
293
+ link: facts.link === null ? null : { ...facts.link },
294
+ deliveryUnknown: [...facts.deliveryUnknown],
295
+ chatDraft: draft === undefined || unwritten(draft)
204
296
  ? null
205
- : { text: draft.text, files: [...draft.files], revision: draft.revision },
297
+ : { text: draft.text, revision: draft.revision },
206
298
  };
207
299
  }
208
- function emit(conversation, messagesChanged = true) {
300
+ function emit(conversation) {
209
301
  if (deps.closed())
210
302
  return;
211
303
  for (const view of views.values()) {
212
- if (view.conversation !== conversation)
304
+ if (view.conversation !== conversation || view.window === null)
213
305
  continue;
214
- if (messagesChanged)
215
- view.messages = null;
216
306
  const ready = [...view.watchers].filter((watcher) => watcher.ready);
217
307
  if (ready.length === 0)
218
308
  continue;
219
- view.messages ??= shownMessages(conversation, view.limit);
220
- const value = viewOf(conversation, view.limit, view.messages);
309
+ const value = viewOf(conversation, view.window, sharedFrame(view, view.window));
221
310
  for (const watcher of ready)
222
311
  watcher.notify(value);
223
312
  }
224
313
  }
314
+ function sharedFrame(view, window) {
315
+ return frameOf(view.asked, window, view.more, view.total);
316
+ }
317
+ async function slide(local, conversation, arriving) {
318
+ let count;
319
+ for (const view of views.values()) {
320
+ if (view.conversation !== conversation || view.window === null)
321
+ continue;
322
+ if (arriving === null) {
323
+ view.window = [];
324
+ view.more = false;
325
+ if (view.total !== null)
326
+ view.total = 0;
327
+ continue;
328
+ }
329
+ const joining = arriving.filter((message) => precedes(message, view.asked.before));
330
+ const next = windowOf(view.window, joining, view.asked.limit);
331
+ view.window = next.messages;
332
+ view.more ||= next.dropped;
333
+ if (view.total !== null && arriving.length > 0) {
334
+ count ??= await local.messageCount(conversation);
335
+ view.total = count;
336
+ }
337
+ }
338
+ }
225
339
  function stateOf(conversation) {
226
340
  const facts = liveOf(conversation);
227
341
  if (facts.questions.length > 0)
@@ -231,34 +345,100 @@ export function createConversationStore(deps) {
231
345
  function entries() {
232
346
  return [...listing.values()].map((entry) => ({
233
347
  conversation: entry.conversation,
234
- agent: { identity: entry.agent, name: agentNames.get(entry.agent) ?? null },
348
+ agent: { identity: entry.agent, name: entry.agentName },
235
349
  name: entry.name,
236
350
  created: entry.created,
237
351
  finished: entry.finished,
238
352
  outcome: entry.outcome,
353
+ waiting: entry.waiting,
239
354
  state: stateOf(entry.conversation),
240
355
  started: lives.get(entry.conversation)?.turn?.started ?? null,
241
356
  }));
242
357
  }
358
+ function keepList() {
359
+ if (!listKnown || listKept)
360
+ return;
361
+ listKept = true;
362
+ queueMicrotask(() => {
363
+ listKept = false;
364
+ if (deps.closed())
365
+ return;
366
+ const entries = [...listing.values()];
367
+ void deps.durably((local) => local.fact('conversationList', entries)).catch(() => { });
368
+ });
369
+ }
243
370
  function emitList() {
244
- if (deps.closed() || listWatchers.size === 0)
371
+ if (deps.closed())
245
372
  return;
246
- const value = entries();
247
- for (const watcher of listWatchers)
248
- watcher(value);
373
+ if (listWatchers.size > 0) {
374
+ const value = entries();
375
+ for (const watcher of listWatchers)
376
+ watcher(value);
377
+ }
378
+ emitAgents();
379
+ learned();
380
+ }
381
+ function learned() {
382
+ if (deps.closed())
383
+ return;
384
+ if (!listKnown && !listHeld && deps.stream() !== undefined && deps.online())
385
+ return;
386
+ if (unlisted.size > 0) {
387
+ const value = entries();
388
+ for (const watcher of [...unlisted]) {
389
+ unlisted.delete(watcher);
390
+ listWatchers.add(watcher);
391
+ watcher(value);
392
+ }
393
+ }
394
+ if (unnamed.size > 0) {
395
+ const value = agents();
396
+ for (const watcher of [...unnamed]) {
397
+ unnamed.delete(watcher);
398
+ agentWatchers.add(watcher);
399
+ watcher(value);
400
+ }
401
+ }
249
402
  }
250
403
  function agents() {
251
- return [...agentNames]
252
- .filter(([identity]) => !retiredAgents.has(identity))
253
- .map(([identity, name]) => ({ identity, name }))
404
+ const named = new Map();
405
+ for (const agent of listedAgents ?? [])
406
+ named.set(agent.agent, agent);
407
+ for (const entry of listing.values()) {
408
+ named.set(entry.agent, { name: entry.agentName, retired: entry.agentRetired });
409
+ }
410
+ return [...named]
411
+ .filter(([, agent]) => !agent.retired)
412
+ .map(([identity, agent]) => ({ identity, name: agent.name }))
254
413
  .sort((left, right) => left.name === right.name
255
414
  ? left.identity.localeCompare(right.identity)
256
415
  : left.name.localeCompare(right.name));
257
416
  }
417
+ function readAgents() {
418
+ const stream = deps.stream();
419
+ if (stream?.agents === undefined || !deps.online() || agentsReading)
420
+ return;
421
+ agentsReading = true;
422
+ void stream
423
+ .agents()
424
+ .then((read) => {
425
+ if (read === null)
426
+ return;
427
+ listedAgents = read;
428
+ emitAgents();
429
+ }, () => { })
430
+ .finally(() => {
431
+ agentsReading = false;
432
+ });
433
+ }
258
434
  function emitAgents() {
259
435
  if (deps.closed() || agentWatchers.size === 0)
260
436
  return;
261
437
  const value = agents();
438
+ const shown = JSON.stringify(value);
439
+ if (shown === agentsShown)
440
+ return;
441
+ agentsShown = shown;
262
442
  for (const watcher of agentWatchers)
263
443
  watcher(value);
264
444
  }
@@ -274,43 +454,40 @@ export function createConversationStore(deps) {
274
454
  if (stateOf(conversation) !== before || next?.turn?.started !== began)
275
455
  emitList();
276
456
  }
277
- async function readAgents() {
278
- const declaration = deps.agents();
279
- if (declaration === undefined)
280
- return;
281
- const { documents } = await deps.documents(declaration.locator);
282
- const names = new Map();
283
- const retired = new Set();
284
- for (const document of documents) {
285
- if (document.type !== declaration.type)
286
- continue;
287
- const identity = document.fields?.[declaration.identity];
288
- const name = document.fields?.[declaration.name];
289
- if (typeof identity !== 'string' || typeof name !== 'string')
290
- continue;
291
- names.set(identity, name);
292
- const ended = declaration.retired === undefined ? null : document.fields?.[declaration.retired];
293
- if (ended !== undefined && ended !== null)
294
- retired.add(identity);
295
- }
296
- const named = JSON.stringify([...names]) !== JSON.stringify([...agentNames]);
297
- if (!named && JSON.stringify([...retired]) === JSON.stringify([...retiredAgents]))
298
- return;
299
- agentNames = names;
300
- retiredAgents = retired;
301
- if (named)
302
- emitList();
303
- emitAgents();
304
- }
305
- function agentsReady() {
306
- agentsRead ??= readAgents().catch(() => {
307
- agentsRead = null;
308
- });
309
- return agentsRead;
457
+ function retiredChat(conversation) {
458
+ if (!conversation.startsWith(NEW_CHAT))
459
+ return false;
460
+ const agent = conversation.slice(NEW_CHAT.length);
461
+ return [...listing.values()].some((entry) => entry.agent === agent && entry.agentRetired);
310
462
  }
311
463
  function live(conversation, change) {
464
+ const owed = unsent.get(conversation);
465
+ for (const fact of FACTS.filter((one) => one in change))
466
+ owed?.delete(fact);
312
467
  setLive(conversation, { ...liveOf(conversation), ...change });
313
- emit(conversation, false);
468
+ emit(conversation);
469
+ }
470
+ function openingEnded() {
471
+ const owed = [...unsent];
472
+ unsent.clear();
473
+ for (const [conversation, facts] of owed) {
474
+ const held = lives.get(conversation);
475
+ if (held === undefined || facts.size === 0)
476
+ continue;
477
+ setLive(conversation, {
478
+ turn: held.turn,
479
+ questions: facts.has('questions') ? QUIET.questions : held.questions,
480
+ process: facts.has('process') ? QUIET.process : held.process,
481
+ commands: facts.has('commands') ? QUIET.commands : held.commands,
482
+ options: facts.has('options') ? QUIET.options : held.options,
483
+ starting: facts.has('starting') ? QUIET.starting : held.starting,
484
+ link: facts.has('link') ? QUIET.link : held.link,
485
+ deliveryUnknown: facts.has('deliveryUnknown')
486
+ ? QUIET.deliveryUnknown
487
+ : held.deliveryUnknown,
488
+ });
489
+ emit(conversation);
490
+ }
314
491
  }
315
492
  function relayed(conversation, turn, operation) {
316
493
  const held = liveOf(conversation).turn;
@@ -339,25 +516,38 @@ export function createConversationStore(deps) {
339
516
  async function remove(leaving) {
340
517
  await deps.durably(async (local) => {
341
518
  await local.dropConversations(leaving);
342
- const removed = [];
343
519
  const changed = [];
344
520
  for (const conversation of leaving) {
345
- for (const message of messages.get(conversation) ?? []) {
346
- removed.push({ conversation, identity: message.identity });
347
- }
348
- if (messages.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
521
+ if (stored.has(conversation) || drafts.has(conversation) || lives.has(conversation)) {
349
522
  changed.push(conversation);
350
523
  }
351
- messages.delete(conversation);
524
+ stored.delete(conversation);
352
525
  reads.delete(conversation);
526
+ partials.delete(conversation);
353
527
  drafts.delete(conversation);
354
528
  behind.delete(conversation);
529
+ unsent.delete(conversation);
355
530
  setLive(conversation, null);
531
+ await slide(local, conversation, null);
356
532
  }
357
533
  if (changed.length > 0)
358
- deps.publish(conversationChangeSet(removed, changed));
534
+ deps.publish(conversationChangeSet(changed));
359
535
  });
360
536
  }
537
+ function missed(conversation) {
538
+ const read = reads.get(conversation);
539
+ const newest = listing.get(conversation)?.newest;
540
+ return (read === undefined ||
541
+ newest === undefined ||
542
+ partials.has(conversation) ||
543
+ (newest !== null && (read === null || newest > read)));
544
+ }
545
+ function noted(conversation, message) {
546
+ const entry = listing.get(conversation);
547
+ if (entry === undefined || (entry.newest !== null && entry.newest >= message.position))
548
+ return;
549
+ listing.set(conversation, { ...entry, newest: message.position });
550
+ }
361
551
  function gap(conversation) {
362
552
  gaps.set(conversation, (gaps.get(conversation) ?? 0) + 1);
363
553
  behind.add(conversation);
@@ -370,33 +560,29 @@ export function createConversationStore(deps) {
370
560
  const advances = read !== undefined &&
371
561
  !behind.has(conversation) &&
372
562
  (read === null || message.position > read);
373
- const sealed = drafts.get(conversation)?.turn === message.turn;
563
+ await sealed(local, conversation, new Set([message.turn]));
374
564
  await local.keepMessages({
375
565
  conversation,
376
566
  messages: [message],
377
- ...(advances ? { read: message.position } : {}),
378
- sealed,
567
+ ...(advances ? { read: { at: message.position, partial: false } } : {}),
379
568
  });
380
- messages.set(conversation, merged(messages.get(conversation) ?? [], [message]));
569
+ stored.add(conversation);
381
570
  if (advances)
382
571
  reads.set(conversation, message.position);
383
- if (sealed)
384
- drafts.delete(conversation);
385
572
  sealTurns(conversation, new Set([message.turn]));
386
- deps.publish(conversationChangeSet([{ conversation, identity: message.identity }], [conversation]));
573
+ await slide(local, conversation, [message]);
574
+ deps.publish(conversationChangeSet([conversation]));
387
575
  });
388
576
  }
389
- async function readHistories(conversations) {
577
+ async function readHistories(conversations, ended) {
390
578
  const generations = new Map(conversations.map((one) => [one, gaps.get(one) ?? 0]));
391
- const starts = new Map(conversations.map((one) => [one, reads.get(one)]));
392
- const arriving = new Map();
393
- const complete = new Set();
394
- let asking = conversations.map((conversation) => ({
579
+ let waiting = conversations.map((conversation) => ({
395
580
  conversation,
396
- after: starts.get(conversation) ?? undefined,
581
+ after: reads.get(conversation) ?? undefined,
397
582
  }));
398
- while (asking.length > 0) {
399
- const result = await deps.call('conversation.history', {
583
+ while (waiting.length > 0) {
584
+ const asking = waiting.slice(0, HISTORY_CONVERSATIONS);
585
+ const result = await deps.history({
400
586
  conversations: asking.map(({ conversation, after }) => ({
401
587
  conversation,
402
588
  ...(after === undefined ? {} : { after }),
@@ -405,46 +591,64 @@ export function createConversationStore(deps) {
405
591
  const pages = historyPages(result);
406
592
  if (pages === null)
407
593
  return result;
408
- const asked = new Set(asking.map(({ conversation }) => conversation));
594
+ const asked = new Map(asking.map(({ conversation, after }) => [conversation, after]));
409
595
  const following = [];
410
596
  for (const page of pages) {
411
597
  if (!asked.has(page.conversation))
412
598
  continue;
413
- arriving.set(page.conversation, [
414
- ...(arriving.get(page.conversation) ?? []),
415
- ...page.messages,
416
- ]);
417
- if (page.next === undefined)
418
- complete.add(page.conversation);
419
- else
599
+ await keepPage(page.conversation, page.messages, asked.get(page.conversation) ?? null, page.next, generations.get(page.conversation) ?? 0);
600
+ if (page.next !== undefined) {
420
601
  following.push({ conversation: page.conversation, after: page.next });
602
+ }
421
603
  }
422
- asking = following;
423
- }
424
- for (const [conversation, held] of arriving) {
425
- if (!complete.has(conversation))
426
- continue;
427
- await keepHistory(conversation, held, starts.get(conversation), generations.get(conversation) ?? 0);
604
+ for (const { conversation } of asking) {
605
+ if (!following.some((next) => next.conversation === conversation))
606
+ ended(conversation);
607
+ }
608
+ waiting = [...waiting.slice(HISTORY_CONVERSATIONS), ...following];
428
609
  }
429
610
  return null;
430
611
  }
431
- function keepHistory(conversation, arriving, from, generation) {
612
+ function caughtUp(conversations) {
613
+ const ends = new Map();
614
+ for (const conversation of conversations) {
615
+ const held = new Promise((end) => ends.set(conversation, end));
616
+ catching.set(conversation, held);
617
+ void held.then(() => {
618
+ if (catching.get(conversation) === held)
619
+ catching.delete(conversation);
620
+ });
621
+ }
622
+ const ended = (conversation) => ends.get(conversation)?.();
623
+ const read = readHistories(conversations, ended);
624
+ const endAll = () => {
625
+ for (const conversation of conversations)
626
+ ended(conversation);
627
+ };
628
+ read.then(endAll, endAll);
629
+ return read;
630
+ }
631
+ function keepPage(conversation, arriving, from, next, generation) {
432
632
  return deps.durably(async (local) => {
433
633
  if (!replicated.has(conversation))
434
634
  return;
435
- const newest = arriving.reduce((top, message) => (top === null || message.position > top ? message.position : top), from ?? null);
436
- const turn = drafts.get(conversation)?.turn;
437
- const sealed = arriving.some((message) => message.turn === turn);
438
- await local.keepMessages({ conversation, messages: arriving, read: newest, sealed });
439
- messages.set(conversation, merged(messages.get(conversation) ?? [], arriving));
440
- reads.set(conversation, newest);
441
- if (sealed)
442
- drafts.delete(conversation);
635
+ const whole = next === undefined;
636
+ const cursor = next ?? newestOf(arriving, from);
637
+ const partial = !whole && (partials.has(conversation) || !reads.has(conversation));
638
+ const sealing = await sealed(local, conversation, new Set(arriving.map((message) => message.turn)));
639
+ await local.keepMessages({ conversation, messages: arriving, read: { at: cursor, partial } });
640
+ stored.add(conversation);
641
+ reads.set(conversation, cursor);
642
+ if (partial)
643
+ partials.add(conversation);
644
+ else
645
+ partials.delete(conversation);
443
646
  sealTurns(conversation, new Set(arriving.map((message) => message.turn)));
444
- if ((gaps.get(conversation) ?? 0) === generation)
647
+ if (whole && (gaps.get(conversation) ?? 0) === generation)
445
648
  behind.delete(conversation);
446
- if (arriving.length > 0 || sealed) {
447
- deps.publish(conversationChangeSet(arriving.map((message) => ({ conversation, identity: message.identity })), [conversation]));
649
+ await slide(local, conversation, arriving);
650
+ if (arriving.length > 0 || sealing || whole) {
651
+ deps.publish(conversationChangeSet([conversation]));
448
652
  }
449
653
  });
450
654
  }
@@ -453,11 +657,8 @@ export function createConversationStore(deps) {
453
657
  if (event.kind === 'gap') {
454
658
  if (replicated.has(conversation))
455
659
  gap(conversation);
456
- const held = lives.get(conversation);
457
- if (held === undefined)
458
- return;
459
- setLive(conversation, { ...QUIET, turn: held.turn });
460
- emit(conversation, false);
660
+ if (lives.has(conversation))
661
+ unsent.set(conversation, new Set(FACTS));
461
662
  return;
462
663
  }
463
664
  if (event.kind === 'turn') {
@@ -480,143 +681,284 @@ export function createConversationStore(deps) {
480
681
  live(conversation, { options: structuredClone(event.options) });
481
682
  return;
482
683
  }
684
+ if (event.kind === 'starting') {
685
+ live(conversation, { starting: event.step });
686
+ return;
687
+ }
688
+ if (event.kind === 'link') {
689
+ live(conversation, { link: { connected: event.connected } });
690
+ return;
691
+ }
692
+ if (event.kind === 'deliveries') {
693
+ live(conversation, { deliveryUnknown: [...event.unknown] });
694
+ return;
695
+ }
483
696
  const arriving = acceptedMessage(event.message);
484
697
  if (arriving === null)
485
698
  return;
699
+ noted(conversation, arriving);
486
700
  if (replicated.has(conversation)) {
487
- void keep(conversation, arriving).catch(() => { });
701
+ if (deps.full()) {
702
+ gap(conversation);
703
+ return;
704
+ }
705
+ void keep(conversation, arriving).catch((error) => {
706
+ if (refusedForSpace(error))
707
+ gap(conversation);
708
+ });
488
709
  return;
489
710
  }
490
711
  if (sealTurns(conversation, new Set([arriving.turn])))
491
712
  emit(conversation);
492
713
  }
714
+ async function kept(local, conversation, following) {
715
+ if (following === null || empty(following)) {
716
+ await local.dropChatDraft(conversation);
717
+ drafts.delete(conversation);
718
+ }
719
+ else {
720
+ await local.keepChatDrafts([following]);
721
+ drafts.set(conversation, following);
722
+ }
723
+ }
724
+ async function sealed(local, conversation, turns) {
725
+ const held = drafts.get(conversation);
726
+ const submitted = held?.submitted ?? [];
727
+ const left = submitted.filter((one) => one.turn === null || !turns.has(one.turn));
728
+ if (held === undefined || left.length === submitted.length)
729
+ return false;
730
+ await kept(local, conversation, withSubmitted(held, left));
731
+ return true;
732
+ }
733
+ function moved(agent, operation, to) {
734
+ const from = newChat(agent);
735
+ return deps.durably(async (local) => {
736
+ const held = drafts.get(from);
737
+ const earlier = (held?.moves ?? []).filter((one) => !deps.expired(one));
738
+ if (earlier.includes(operation))
739
+ return;
740
+ const blank = {
741
+ conversation: from,
742
+ text: '',
743
+ revision: chatDraftRevision(),
744
+ };
745
+ const { moves: _answered, ...unsent } = held ?? blank;
746
+ const taken = drafts.has(to) || empty(unsent) ? [] : [{ ...unsent, conversation: to }];
747
+ const left = { ...(taken.length === 0 ? unsent : blank), moves: [...earlier, operation] };
748
+ await local.keepChatDrafts([left, ...taken]);
749
+ drafts.set(from, left);
750
+ for (const one of taken)
751
+ drafts.set(to, one);
752
+ deps.publish(conversationChangeSet([from, to]));
753
+ });
754
+ }
493
755
  function withDraft(conversation, next) {
494
756
  return deps.durably(async (local) => {
495
- const following = next(drafts.get(conversation));
757
+ const following = await next(drafts.get(conversation), local);
496
758
  if (following === undefined)
497
759
  return;
498
- if (following === null) {
499
- await local.dropChatDraft(conversation);
500
- drafts.delete(conversation);
501
- }
502
- else {
503
- await local.keepChatDraft(following);
504
- drafts.set(conversation, following);
505
- }
506
- deps.publish(conversationChangeSet([], [conversation]));
760
+ await kept(local, conversation, following);
761
+ deps.publish(conversationChangeSet([conversation]));
507
762
  });
508
763
  }
509
- function invoke(name, conversation, extra = {}) {
510
- return deps.call(name, { conversation, ...extra });
764
+ async function submitted(conversation, operation, draft) {
765
+ let submission;
766
+ let earlier = false;
767
+ await withDraft(conversation, (current) => {
768
+ submission = current?.submitted?.find((one) => one.operation === operation);
769
+ earlier = submission !== undefined;
770
+ if (submission !== undefined)
771
+ return undefined;
772
+ const said = draft ?? current;
773
+ submission = { operation, text: said?.text ?? '', turn: null };
774
+ return {
775
+ conversation,
776
+ ...(draft === undefined || current === undefined
777
+ ? { text: '', revision: chatDraftRevision() }
778
+ : { text: current.text, revision: current.revision }),
779
+ submitted: [...(current?.submitted ?? []), submission],
780
+ };
781
+ });
782
+ return { submission: submission, earlier };
783
+ }
784
+ function forgotten(conversation, submission) {
785
+ return withDraft(conversation, (current) => {
786
+ const held = current?.submitted ?? [];
787
+ if (current === undefined || !held.includes(submission))
788
+ return undefined;
789
+ return withSubmitted(current, held.filter((one) => one !== submission));
790
+ });
791
+ }
792
+ async function dispatched(conversation, submission) {
793
+ const dispatch = { answered: false };
794
+ sending.set(submission.operation, dispatch);
795
+ try {
796
+ const result = await invoke('conversations/send', conversation, {
797
+ operation_id: submission.operation,
798
+ text: submission.text,
799
+ });
800
+ dispatch.answered = result.kind !== 'transport-uncertainty';
801
+ if (result.kind === 'revoked-authorization')
802
+ return result;
803
+ if (result.kind !== 'action' && result.retryable) {
804
+ deps.unanswered();
805
+ return result;
806
+ }
807
+ const turn = answered(result, 'message_id');
808
+ await withDraft(conversation, async (current, local) => {
809
+ const held = current?.submitted?.find((one) => one.operation === submission.operation);
810
+ if (current === undefined || held === undefined || held.turn !== null)
811
+ return undefined;
812
+ const others = current.submitted.filter((one) => one !== held);
813
+ if (result.kind !== 'action')
814
+ return returned(current, held, others);
815
+ const arrived = turn !== null && (await local.turnArrived(conversation, turn));
816
+ return withSubmitted(current, turn === null || arrived
817
+ ? others
818
+ : current.submitted.map((one) => (one === held ? { ...held, turn } : one)));
819
+ });
820
+ return result;
821
+ }
822
+ finally {
823
+ if (sending.get(submission.operation) === dispatch)
824
+ sending.delete(submission.operation);
825
+ }
826
+ }
827
+ function invoke(name, conversation, extra = { operation_id: changeBatchIdentity() }) {
828
+ return deps.call(name, { conversation_id: conversation, ...extra });
511
829
  }
512
830
  const surface = {
513
- async list() {
514
- await agentsReady();
515
- return entries();
831
+ list() {
832
+ return new Promise((settle) => {
833
+ const answer = (listed) => {
834
+ unlisted.delete(answer);
835
+ listWatchers.delete(answer);
836
+ settle(listed);
837
+ };
838
+ unlisted.add(answer);
839
+ learned();
840
+ });
516
841
  },
517
842
  async read(conversation, options = {}) {
518
- const limit = limitOf(options);
519
- return deps.durably(() => Promise.resolve(viewOf(conversation, limit)));
843
+ const asked = askedOf(options);
844
+ return deps.durably(async (local) => {
845
+ const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
846
+ const total = asked.total ? await local.messageCount(conversation) : null;
847
+ return structuredClone(viewOf(conversation, messages, frameOf(asked, messages, more, total)));
848
+ });
520
849
  },
521
- create(input) {
522
- return deps.call('conversation.create', input);
850
+ async create(input) {
851
+ const result = await deps.call('conversations/create', input);
852
+ const conversation = answered(result, 'conversation_id');
853
+ if (conversation === null)
854
+ return result;
855
+ if (!listing.has(conversation))
856
+ creations.add(conversation);
857
+ const creation = creationOf(input);
858
+ if (creation !== null)
859
+ await moved(creation.agent, creation.operation, conversation);
860
+ return result;
523
861
  },
524
- async send(conversation) {
525
- const draft = drafts.get(conversation);
526
- const pending = draft === undefined ? undefined : { draft };
527
- if (pending !== undefined) {
528
- sending.set(conversation, pending);
529
- deps.publish(conversationChangeSet([], [conversation]));
530
- }
531
- try {
532
- const files = await Promise.all((draft?.files ?? []).map(async (file) => ({
533
- ...(await declaredFile(file)),
534
- blob: file.blob,
535
- })));
536
- const result = await invoke('conversation.send', conversation, {
537
- text: draft?.text ?? '',
538
- files,
539
- });
540
- const turn = turnOf(result);
541
- if (turn === null || draft === undefined)
542
- return result;
543
- await withDraft(conversation, (current) => {
544
- if (current !== draft)
545
- return undefined;
546
- const sealed = (messages.get(conversation) ?? []).some((message) => message.turn === turn);
547
- return sealed ? null : { ...draft, turn };
862
+ async send(conversation, operation = changeBatchIdentity(), draft) {
863
+ if (deps.full())
864
+ return DURABLE_WRITE;
865
+ if (deps.online()) {
866
+ const held = await submitted(conversation, operation, draft).catch((error) => {
867
+ if (refusedForSpace(error))
868
+ return null;
869
+ throw error;
548
870
  });
549
- return result;
871
+ if (held === null)
872
+ return DURABLE_WRITE;
873
+ const { submission, earlier } = held;
874
+ if (!earlier || !deps.expired(operation))
875
+ return dispatched(conversation, submission);
876
+ await forgotten(conversation, submission);
877
+ return EXPIRED;
550
878
  }
551
- finally {
552
- if (pending !== undefined && sending.get(conversation) === pending) {
553
- sending.delete(conversation);
554
- if (drafts.get(conversation) === draft) {
555
- deps.publish(conversationChangeSet([], [conversation]));
556
- }
557
- }
879
+ if (draft !== undefined) {
880
+ await withDraft(conversation, (current) => current?.submitted?.some((one) => one.operation === operation) === true
881
+ ? undefined
882
+ : returned(current ?? { conversation, text: '', revision: '' }, { operation, ...draft, turn: null }, current?.submitted ?? []));
558
883
  }
884
+ return OFFLINE;
559
885
  },
560
886
  answer(conversation, answer) {
561
- return invoke('conversation.answer', conversation, { answer });
887
+ return invoke('conversations/interaction/answer', conversation, {
888
+ operation_id: changeBatchIdentity(),
889
+ ...answer,
890
+ });
562
891
  },
563
892
  interrupt(conversation) {
564
- return invoke('conversation.interrupt', conversation);
893
+ return invoke('conversations/interrupt', conversation);
565
894
  },
566
895
  kill(conversation) {
567
- return invoke('conversation.kill', conversation);
568
- },
569
- async cancel(conversation, turn) {
570
- const result = await invoke('conversation.cancel', conversation, { turn });
571
- if (result.kind !== 'action')
572
- return result;
573
- await withDraft(conversation, (current) => current === undefined || current.turn !== turn ? undefined : { ...current, turn: null });
574
- return result;
896
+ return invoke('conversations/kill', conversation);
575
897
  },
576
898
  delete(conversation) {
577
- return invoke('conversation.delete', conversation);
899
+ return invoke('conversations/delete', conversation);
578
900
  },
579
901
  };
580
902
  const chatDraft = {
581
903
  write(conversation, draft, revision = chatDraftRevision()) {
582
- return withDraft(conversation, () => ({
904
+ return withDraft(conversation, (current) => ({
583
905
  conversation,
584
906
  text: draft.text,
585
- files: [...draft.files],
586
- turn: null,
587
907
  revision,
908
+ ...(current === undefined ? {} : pending(current)),
909
+ }));
910
+ },
911
+ prepend(conversation, draft) {
912
+ return withDraft(conversation, (current) => ({
913
+ conversation,
914
+ ...(current === undefined ? { text: draft.text } : prepended(current, draft)),
915
+ revision: chatDraftRevision(),
916
+ ...(current === undefined ? {} : pending(current)),
588
917
  }));
589
918
  },
590
919
  discard(conversation) {
591
- return withDraft(conversation, (current) => (current === undefined ? undefined : null));
920
+ return withDraft(conversation, (current) => current === undefined
921
+ ? undefined
922
+ : { conversation, text: '', revision: chatDraftRevision(), ...pending(current) });
592
923
  },
593
924
  };
594
925
  return {
595
926
  surface,
596
927
  chatDraft,
597
- hydrate(held, stored) {
598
- messages.clear();
928
+ hydrate(held, kept, list) {
929
+ if (list !== null && !listKnown) {
930
+ listing.clear();
931
+ for (const entry of list)
932
+ listing.set(entry.conversation, { ...entry });
933
+ listHeld = true;
934
+ }
935
+ stored.clear();
599
936
  reads.clear();
600
- for (const view of views.values())
601
- view.messages = null;
937
+ partials.clear();
602
938
  for (const entry of held) {
603
- messages.set(entry.conversation, [...entry.messages].sort(ordered));
939
+ stored.add(entry.conversation);
604
940
  if (entry.read !== undefined)
605
941
  reads.set(entry.conversation, entry.read);
942
+ if (entry.partial)
943
+ partials.add(entry.conversation);
606
944
  }
607
945
  drafts.clear();
608
- for (const record of stored)
946
+ for (const record of kept)
609
947
  drafts.set(record.conversation, record);
948
+ if (listHeld)
949
+ learned();
610
950
  },
611
951
  async reconcile() {
612
952
  if (!listKnown)
613
953
  return;
614
954
  const named = new Set(listing.keys());
615
955
  const leaving = [
616
- ...new Set([...replicated, ...messages.keys(), ...reads.keys(), ...drafts.keys()]),
617
- ].filter((conversation) => !named.has(conversation));
956
+ ...new Set([...replicated, ...stored, ...reads.keys(), ...drafts.keys()]),
957
+ ].filter((conversation) => !named.has(conversation) &&
958
+ !creations.has(conversation) &&
959
+ (!conversation.startsWith(NEW_CHAT) || retiredChat(conversation)));
618
960
  for (const conversation of named) {
619
- if (!replicated.has(conversation))
961
+ if (!replicated.has(conversation) && missed(conversation))
620
962
  behind.add(conversation);
621
963
  }
622
964
  replicated = named;
@@ -625,44 +967,100 @@ export function createConversationStore(deps) {
625
967
  await remove(leaving);
626
968
  },
627
969
  async catchUp(progress) {
628
- const reading = [...behind].filter((conversation) => replicated.has(conversation)).sort();
629
- if (reading.length > 0 && !deps.closed()) {
630
- const failed = await readHistories(reading);
970
+ if (deps.full()) {
971
+ if (behind.size > 0)
972
+ progress.complete = false;
973
+ return;
974
+ }
975
+ const owed = (conversation) => behind.has(conversation) && replicated.has(conversation);
976
+ const read = async (conversations) => {
977
+ if (conversations.length === 0 || deps.closed())
978
+ return;
979
+ const failed = await caughtUp(conversations);
631
980
  if (failed?.kind === 'transport-uncertainty')
632
981
  progress.reachable = false;
633
- }
982
+ };
983
+ const asking = [...behind].filter(owed).sort();
984
+ const awaited = asking.filter((conversation) => catching.has(conversation));
985
+ await Promise.all([
986
+ read(asking.filter((conversation) => !catching.has(conversation))),
987
+ Promise.all(awaited.map((conversation) => catching.get(conversation))).then(() => read(awaited.filter((conversation) => owed(conversation) && !catching.has(conversation)))),
988
+ ]);
634
989
  if (behind.size > 0)
635
990
  progress.complete = false;
636
991
  },
992
+ async resend(progress) {
993
+ for (const [conversation, held] of [...drafts]) {
994
+ for (const submission of held.submitted ?? []) {
995
+ if (submission.turn !== null || sending.has(submission.operation))
996
+ continue;
997
+ if (deps.closed())
998
+ return;
999
+ if (deps.expired(submission.operation)) {
1000
+ await forgotten(conversation, submission);
1001
+ continue;
1002
+ }
1003
+ const result = await dispatched(conversation, submission);
1004
+ if (result.kind === 'revoked-authorization')
1005
+ return;
1006
+ if (result.kind === 'transport-uncertainty') {
1007
+ progress.reachable = false;
1008
+ return;
1009
+ }
1010
+ if (result.kind !== 'action' && result.retryable) {
1011
+ progress.complete = false;
1012
+ return;
1013
+ }
1014
+ }
1015
+ }
1016
+ },
637
1017
  current() {
638
1018
  return behind.size === 0;
639
1019
  },
640
- reconnected() {
641
- for (const conversation of replicated)
642
- gap(conversation);
1020
+ known() {
1021
+ return listKnown;
1022
+ },
1023
+ reopened() {
1024
+ for (const [operation, dispatch] of sending)
1025
+ if (!dispatch.answered)
1026
+ sending.delete(operation);
1027
+ },
1028
+ lost() {
1029
+ catching.clear();
643
1030
  },
644
1031
  watch(conversation, options, deliver) {
645
- const limit = limitOf(options);
646
- const key = JSON.stringify([conversation, limit ?? null]);
1032
+ const asked = askedOf(options);
1033
+ const key = JSON.stringify([conversation, asked.limit, asked.after ?? null, asked.total]);
647
1034
  const view = views.get(key) ?? {
648
1035
  conversation,
649
- limit,
1036
+ asked,
650
1037
  watchers: new Set(),
651
- messages: null,
1038
+ window: null,
1039
+ more: false,
1040
+ total: null,
652
1041
  };
653
1042
  views.set(key, view);
654
1043
  const watcher = { notify: deliver, ready: false };
655
1044
  view.watchers.add(watcher);
656
- void deps
657
- .durably(() => Promise.resolve())
1045
+ const reading = new AbortController();
1046
+ void untilAnswered(() => deps.durably(async (local) => {
1047
+ if (view.window !== null)
1048
+ return;
1049
+ const { messages, more } = await local.messageWindow(conversation, asked.limit, asked.before);
1050
+ view.window = messages;
1051
+ view.more = more;
1052
+ view.total = asked.total ? await local.messageCount(conversation) : null;
1053
+ }), reading.signal)
658
1054
  .then(() => {
659
1055
  if (deps.closed() || !view.watchers.has(watcher))
660
1056
  return;
661
1057
  watcher.ready = true;
662
- watcher.notify(viewOf(conversation, limit));
1058
+ const window = view.window ?? [];
1059
+ watcher.notify(viewOf(conversation, window, sharedFrame(view, window)));
663
1060
  })
664
1061
  .catch(() => { });
665
1062
  return () => {
1063
+ reading.abort();
666
1064
  if (!view.watchers.delete(watcher))
667
1065
  return;
668
1066
  if (view.watchers.size === 0)
@@ -670,35 +1068,26 @@ export function createConversationStore(deps) {
670
1068
  };
671
1069
  },
672
1070
  watchList(deliver) {
673
- listWatchers.add(deliver);
674
- void agentsReady().then(() => {
675
- if (!deps.closed() && listWatchers.has(deliver))
676
- deliver(entries());
677
- });
1071
+ unlisted.add(deliver);
1072
+ learned();
678
1073
  return () => {
1074
+ unlisted.delete(deliver);
679
1075
  listWatchers.delete(deliver);
680
1076
  };
681
1077
  },
682
1078
  watchAgents(deliver) {
683
- agentWatchers.add(deliver);
684
- void agentsReady().then(() => {
685
- if (!deps.closed() && agentWatchers.has(deliver))
686
- deliver(agents());
687
- });
1079
+ unnamed.add(deliver);
1080
+ learned();
1081
+ readAgents();
688
1082
  return () => {
1083
+ unnamed.delete(deliver);
689
1084
  agentWatchers.delete(deliver);
690
1085
  };
691
1086
  },
1087
+ learned,
692
1088
  changed(set) {
693
1089
  for (const conversation of set.conversations)
694
1090
  emit(conversation);
695
- const declaration = deps.agents();
696
- if (declaration !== undefined &&
697
- set.documents.some((change) => change.locator === declaration.locator)) {
698
- agentsRead = readAgents().catch(() => {
699
- agentsRead = null;
700
- });
701
- }
702
1091
  },
703
1092
  receive(event) {
704
1093
  if (deps.closed())
@@ -706,37 +1095,53 @@ export function createConversationStore(deps) {
706
1095
  if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted') {
707
1096
  if (event.kind === 'list') {
708
1097
  listing.clear();
709
- for (const entry of event.entries)
1098
+ for (const entry of event.entries) {
710
1099
  listing.set(entry.conversation, { ...entry });
1100
+ creations.delete(entry.conversation);
1101
+ }
711
1102
  listKnown = true;
1103
+ for (const conversation of replicated) {
1104
+ if (!listing.has(conversation))
1105
+ continue;
1106
+ if (!missed(conversation))
1107
+ behind.delete(conversation);
1108
+ else if (!behind.has(conversation))
1109
+ gap(conversation);
1110
+ }
1111
+ openingEnded();
712
1112
  }
713
1113
  else if (event.kind === 'listed') {
714
1114
  listing.set(event.entry.conversation, { ...event.entry });
1115
+ creations.delete(event.entry.conversation);
715
1116
  }
716
1117
  else {
717
1118
  listing.delete(event.conversation);
718
1119
  }
1120
+ keepList();
719
1121
  emitList();
720
- return (event.kind !== 'list' &&
1122
+ return ((event.kind !== 'list' &&
721
1123
  listKnown &&
722
1124
  (listing.size !== replicated.size ||
723
- [...listing.keys()].some((conversation) => !replicated.has(conversation))));
1125
+ [...listing.keys()].some((conversation) => !replicated.has(conversation)))) ||
1126
+ (listKnown && [...drafts.keys()].some(retiredChat)));
724
1127
  }
725
1128
  heard(event);
726
1129
  return false;
727
1130
  },
728
- messages() {
729
- return [...messages].flatMap(([conversation, held]) => held.map(({ identity, text }) => ({ conversation, identity, text })));
1131
+ searchable() {
1132
+ return [...(listKnown ? listing.keys() : stored)].sort();
730
1133
  },
731
- message(conversation, identity) {
732
- const held = messages.get(conversation)?.find((message) => message.identity === identity);
733
- return held === undefined ? null : { conversation, identity, text: held.text };
1134
+ acquired(conversation) {
1135
+ return reads.has(conversation) && !partials.has(conversation);
734
1136
  },
735
1137
  close() {
736
1138
  views.clear();
737
1139
  lives.clear();
1140
+ unsent.clear();
738
1141
  listWatchers.clear();
1142
+ unlisted.clear();
739
1143
  agentWatchers.clear();
1144
+ unnamed.clear();
740
1145
  },
741
1146
  };
742
1147
  }