@agentshouse/mdruntime 0.1.0-c6e4c19c13638 → 0.1.0-c6fd2cbbff187

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