@agentshouse/mdruntime 0.1.0-c698c0ebd65bd → 0.1.0-c6dd3e87e3f5e

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