@agentshouse/mdruntime 0.1.0-c8ca66ac9e99d → 0.1.0-c8eac46931574

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