@agentshouse/mdruntime 0.1.0-c454894e67f74 → 0.1.0-c4ca57eefb98a

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