@agentshouse/mdruntime 0.1.0-c95464765b116 → 0.1.0-c95f0f04f2113

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