@agentshouse/mdruntime 0.1.0-c3903a68dcbbf → 0.1.0-c435c7a7c4f4e

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