@agentshouse/mdruntime 0.1.0-cdb8840578a20 → 0.1.0-cdded6c080913

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