@agentshouse/mdruntime 0.1.0-c97456d63baba → 0.1.0-c99ee6ff87175

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 (234) hide show
  1. package/README.md +19 -16
  2. package/dist/adapter.d.ts +145 -70
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/attached-model.d.ts +5 -1
  5. package/dist/attached-model.d.ts.map +1 -1
  6. package/dist/attached-model.js +38 -4
  7. package/dist/binary.d.ts +0 -5
  8. package/dist/binary.d.ts.map +1 -1
  9. package/dist/binary.js +1 -1
  10. package/dist/bootstrap.d.ts +26 -10
  11. package/dist/bootstrap.d.ts.map +1 -1
  12. package/dist/bootstrap.js +180 -99
  13. package/dist/chain.d.ts +11 -0
  14. package/dist/chain.d.ts.map +1 -0
  15. package/dist/chain.js +64 -0
  16. package/dist/change-set.d.ts +9 -16
  17. package/dist/change-set.d.ts.map +1 -1
  18. package/dist/change-set.js +11 -88
  19. package/dist/change.d.ts +31 -1
  20. package/dist/change.d.ts.map +1 -1
  21. package/dist/change.js +10 -1
  22. package/dist/client.d.ts +17 -9
  23. package/dist/client.d.ts.map +1 -1
  24. package/dist/client.js +95 -11
  25. package/dist/collection.d.ts +10 -21
  26. package/dist/collection.d.ts.map +1 -1
  27. package/dist/collection.js +39 -291
  28. package/dist/compile.d.ts +16 -0
  29. package/dist/compile.d.ts.map +1 -0
  30. package/dist/compile.js +789 -0
  31. package/dist/connection.d.ts +28 -15
  32. package/dist/connection.d.ts.map +1 -1
  33. package/dist/connection.js +3294 -1360
  34. package/dist/context.d.ts +2 -0
  35. package/dist/context.d.ts.map +1 -0
  36. package/dist/{chat/context.js → context.js} +1 -4
  37. package/dist/conversation.d.ts +30 -14
  38. package/dist/conversation.d.ts.map +1 -1
  39. package/dist/conversation.js +462 -203
  40. package/dist/digest.d.ts +7 -0
  41. package/dist/digest.d.ts.map +1 -0
  42. package/dist/digest.js +113 -0
  43. package/dist/durability.d.ts +2 -5
  44. package/dist/durability.d.ts.map +1 -1
  45. package/dist/durability.js +5 -0
  46. package/dist/entry.d.ts +4 -1
  47. package/dist/entry.d.ts.map +1 -1
  48. package/dist/entry.js +19 -23
  49. package/dist/file.d.ts +2 -19
  50. package/dist/file.d.ts.map +1 -1
  51. package/dist/file.js +7 -122
  52. package/dist/host.d.ts +6 -8
  53. package/dist/host.d.ts.map +1 -1
  54. package/dist/host.js +93 -23
  55. package/dist/identity.d.ts +0 -1
  56. package/dist/identity.d.ts.map +1 -1
  57. package/dist/identity.js +0 -3
  58. package/dist/index.d.ts +18 -12
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +8 -1
  61. package/dist/job.d.ts +14 -0
  62. package/dist/job.d.ts.map +1 -0
  63. package/dist/job.js +28 -0
  64. package/dist/live.d.ts +23 -0
  65. package/dist/live.d.ts.map +1 -0
  66. package/dist/live.js +127 -0
  67. package/dist/media.d.ts +8 -0
  68. package/dist/media.d.ts.map +1 -0
  69. package/dist/media.js +218 -0
  70. package/dist/memory.d.ts +16 -7
  71. package/dist/memory.d.ts.map +1 -1
  72. package/dist/memory.js +208 -102
  73. package/dist/messages.d.ts +28 -0
  74. package/dist/messages.d.ts.map +1 -0
  75. package/dist/messages.js +89 -0
  76. package/dist/model.d.ts +6 -1
  77. package/dist/model.d.ts.map +1 -1
  78. package/dist/model.js +7 -10
  79. package/dist/name.d.ts +4 -0
  80. package/dist/name.d.ts.map +1 -0
  81. package/dist/name.js +122 -0
  82. package/dist/observation.d.ts +17 -2
  83. package/dist/observation.d.ts.map +1 -1
  84. package/dist/observation.js +100 -5
  85. package/dist/opened.d.ts +22 -0
  86. package/dist/opened.d.ts.map +1 -0
  87. package/dist/opened.js +107 -0
  88. package/dist/outcome.d.ts +45 -7
  89. package/dist/outcome.d.ts.map +1 -1
  90. package/dist/outcome.js +39 -0
  91. package/dist/overlay.d.ts +3 -5
  92. package/dist/overlay.d.ts.map +1 -1
  93. package/dist/overlay.js +95 -156
  94. package/dist/paths.d.ts +1 -0
  95. package/dist/paths.d.ts.map +1 -1
  96. package/dist/paths.js +4 -0
  97. package/dist/phrase.d.ts +3 -0
  98. package/dist/phrase.d.ts.map +1 -0
  99. package/dist/phrase.js +8 -0
  100. package/dist/query.d.ts +42 -2
  101. package/dist/query.d.ts.map +1 -1
  102. package/dist/query.js +67 -7
  103. package/dist/reach.d.ts +16 -8
  104. package/dist/reach.d.ts.map +1 -1
  105. package/dist/reach.js +219 -90
  106. package/dist/react.d.ts +4 -3
  107. package/dist/react.d.ts.map +1 -1
  108. package/dist/react.js +7 -17
  109. package/dist/readiness.d.ts +14 -17
  110. package/dist/readiness.d.ts.map +1 -1
  111. package/dist/readiness.js +2 -8
  112. package/dist/recovery.d.ts +3 -2
  113. package/dist/recovery.d.ts.map +1 -1
  114. package/dist/recovery.js +34 -7
  115. package/dist/refresh.d.ts +17 -14
  116. package/dist/refresh.d.ts.map +1 -1
  117. package/dist/refresh.js +155 -52
  118. package/dist/replica.d.ts +55 -33
  119. package/dist/replica.d.ts.map +1 -1
  120. package/dist/replica.js +300 -182
  121. package/dist/room-draft.d.ts +35 -4
  122. package/dist/room-draft.d.ts.map +1 -1
  123. package/dist/room-draft.js +161 -74
  124. package/dist/rows.d.ts +39 -29
  125. package/dist/rows.d.ts.map +1 -1
  126. package/dist/rows.js +178 -94
  127. package/dist/save.d.ts +53 -14
  128. package/dist/save.d.ts.map +1 -1
  129. package/dist/save.js +529 -185
  130. package/dist/search.d.ts +15 -77
  131. package/dist/search.d.ts.map +1 -1
  132. package/dist/search.js +10 -184
  133. package/dist/seed.d.ts +16 -0
  134. package/dist/seed.d.ts.map +1 -0
  135. package/dist/seed.js +75 -0
  136. package/dist/shared.d.ts +7 -3
  137. package/dist/shared.d.ts.map +1 -1
  138. package/dist/shared.js +119 -66
  139. package/dist/status.d.ts +4 -3
  140. package/dist/status.d.ts.map +1 -1
  141. package/dist/status.js +3 -1
  142. package/dist/store.d.ts +99 -97
  143. package/dist/store.d.ts.map +1 -1
  144. package/dist/store.js +336 -660
  145. package/dist/stored.d.ts +162 -0
  146. package/dist/stored.d.ts.map +1 -0
  147. package/dist/stored.js +549 -0
  148. package/dist/tables.d.ts +353 -0
  149. package/dist/tables.d.ts.map +1 -0
  150. package/dist/tables.js +585 -0
  151. package/dist/transfer.d.ts +0 -5
  152. package/dist/transfer.d.ts.map +1 -1
  153. package/dist/turn.d.ts +5 -3
  154. package/dist/turn.d.ts.map +1 -1
  155. package/dist/turn.js +51 -21
  156. package/dist/validation.d.ts +29 -0
  157. package/dist/validation.d.ts.map +1 -0
  158. package/dist/validation.js +101 -0
  159. package/dist/windows.d.ts +22 -0
  160. package/dist/windows.d.ts.map +1 -0
  161. package/dist/windows.js +89 -0
  162. package/dist/wire.d.ts +34 -5
  163. package/dist/wire.d.ts.map +1 -1
  164. package/dist/wire.js +10 -2
  165. package/package.json +18 -19
  166. package/AGENTS.md +0 -299
  167. package/dist/acquisition.d.ts +0 -14
  168. package/dist/acquisition.d.ts.map +0 -1
  169. package/dist/acquisition.js +0 -24
  170. package/dist/chat/acts.d.ts +0 -33
  171. package/dist/chat/acts.d.ts.map +0 -1
  172. package/dist/chat/acts.js +0 -36
  173. package/dist/chat/context.d.ts +0 -3
  174. package/dist/chat/context.d.ts.map +0 -1
  175. package/dist/chat/input.d.ts +0 -9
  176. package/dist/chat/input.d.ts.map +0 -1
  177. package/dist/chat/input.js +0 -99
  178. package/dist/chat/kit.d.ts +0 -61
  179. package/dist/chat/kit.d.ts.map +0 -1
  180. package/dist/chat/kit.js +0 -123
  181. package/dist/chat/lasting.d.ts +0 -6
  182. package/dist/chat/lasting.d.ts.map +0 -1
  183. package/dist/chat/lasting.js +0 -19
  184. package/dist/chat/list.d.ts +0 -27
  185. package/dist/chat/list.d.ts.map +0 -1
  186. package/dist/chat/list.js +0 -63
  187. package/dist/chat/markdown.d.ts +0 -20
  188. package/dist/chat/markdown.d.ts.map +0 -1
  189. package/dist/chat/markdown.js +0 -188
  190. package/dist/chat/message.d.ts +0 -6
  191. package/dist/chat/message.d.ts.map +0 -1
  192. package/dist/chat/message.js +0 -192
  193. package/dist/chat/messages.d.ts +0 -22
  194. package/dist/chat/messages.d.ts.map +0 -1
  195. package/dist/chat/messages.js +0 -155
  196. package/dist/chat/name.d.ts +0 -6
  197. package/dist/chat/name.d.ts.map +0 -1
  198. package/dist/chat/name.js +0 -37
  199. package/dist/chat/opened.d.ts +0 -11
  200. package/dist/chat/opened.d.ts.map +0 -1
  201. package/dist/chat/opened.js +0 -17
  202. package/dist/chat/overlay.d.ts +0 -31
  203. package/dist/chat/overlay.d.ts.map +0 -1
  204. package/dist/chat/overlay.js +0 -97
  205. package/dist/chat/parts.d.ts +0 -57
  206. package/dist/chat/parts.d.ts.map +0 -1
  207. package/dist/chat/parts.js +0 -119
  208. package/dist/chat/process.d.ts +0 -20
  209. package/dist/chat/process.d.ts.map +0 -1
  210. package/dist/chat/process.js +0 -32
  211. package/dist/chat/provider.d.ts +0 -63
  212. package/dist/chat/provider.d.ts.map +0 -1
  213. package/dist/chat/provider.js +0 -629
  214. package/dist/chat/question.d.ts +0 -9
  215. package/dist/chat/question.d.ts.map +0 -1
  216. package/dist/chat/question.js +0 -80
  217. package/dist/chat/questions.d.ts +0 -31
  218. package/dist/chat/questions.d.ts.map +0 -1
  219. package/dist/chat/questions.js +0 -111
  220. package/dist/chat/read.d.ts +0 -5
  221. package/dist/chat/read.d.ts.map +0 -1
  222. package/dist/chat/read.js +0 -13
  223. package/dist/chat/ready.d.ts +0 -34
  224. package/dist/chat/ready.d.ts.map +0 -1
  225. package/dist/chat/ready.js +0 -91
  226. package/dist/chat/style.d.ts +0 -4
  227. package/dist/chat/style.d.ts.map +0 -1
  228. package/dist/chat/style.js +0 -302
  229. package/dist/chat.d.ts +0 -14
  230. package/dist/chat.d.ts.map +0 -1
  231. package/dist/chat.js +0 -8
  232. package/dist/read-side.d.ts +0 -25
  233. package/dist/read-side.d.ts.map +0 -1
  234. package/dist/read-side.js +0 -639
package/dist/memory.js CHANGED
@@ -1,4 +1,8 @@
1
1
  import { sameChange } from './overlay.js';
2
+ const ATTACHMENTS = 'attachments';
3
+ function lastName(path) {
4
+ return path.slice(path.lastIndexOf('/') + 1);
5
+ }
2
6
  const FIXTURE_RETENTION = {
3
7
  history: { kind: 'duration', seconds: 1 },
4
8
  outcome: 604_800,
@@ -15,12 +19,7 @@ function declaredRetention(value) {
15
19
  }
16
20
  const MODEL_ABSENT = { kind: 'model-unavailable', reason: 'not-readable' };
17
21
  function modelsOf(room) {
18
- return {
19
- collection: room.model ?? MODEL_ABSENT,
20
- ...(room.typedSelection === undefined
21
- ? {}
22
- : { selection: room.selectionModel ?? MODEL_ABSENT }),
23
- };
22
+ return { collection: room.model ?? MODEL_ABSENT };
24
23
  }
25
24
  function changedModels(room, from) {
26
25
  const now = modelsOf(room);
@@ -53,14 +52,16 @@ export function createMemoryAdapter(options = {}) {
53
52
  const listeners = new Set();
54
53
  const modeObservers = new Set();
55
54
  const scripted = new Map();
56
- const unsealed = new Map();
57
- const sealed = new Set();
58
55
  const sentTurns = new Map();
59
56
  const historyPageSize = options.historyPageSize;
60
- let held = [];
61
- let heldDemand = null;
57
+ const attached = new Map();
58
+ const linked = new WeakMap();
59
+ const carried = new Map();
62
60
  let expiring = [];
61
+ let openings = 0;
62
+ const declaredRooms = new Set();
63
63
  let replicated = [];
64
+ let heldCursor = null;
64
65
  let issued = 0;
65
66
  function scriptConversations(entries) {
66
67
  scripted.clear();
@@ -88,6 +89,19 @@ export function createMemoryAdapter(options = {}) {
88
89
  const wanted = ordinals.get(`${locator}\u0000${target}`);
89
90
  return held !== undefined && wanted !== undefined && held >= wanted;
90
91
  }
92
+ function heldBlob(room, path) {
93
+ return attached.get(`${room.locator}\u0000${path}`) ?? room.blobs?.[path];
94
+ }
95
+ function freePath(room, name) {
96
+ const dot = name.lastIndexOf('.');
97
+ const stem = dot > 0 ? name.slice(0, dot) : name;
98
+ const extension = dot > 0 ? name.slice(dot) : '';
99
+ for (let count = 1;; count += 1) {
100
+ const path = `${ATTACHMENTS}/${count === 1 ? stem : `${stem}-${count}`}${extension}`;
101
+ if (heldBlob(room, path) === undefined && room.documents?.[path] === undefined)
102
+ return path;
103
+ }
104
+ }
91
105
  function retain(entries) {
92
106
  for (const room of entries) {
93
107
  note(room.locator, room);
@@ -123,22 +137,37 @@ export function createMemoryAdapter(options = {}) {
123
137
  ...(opening ? { models: modelsOf(room) } : {}),
124
138
  documents: named(room.documents ?? {}, taken),
125
139
  revisions: named(room.revisions ?? {}, [...taken, ...targets]),
140
+ ...(room.fileIds === undefined
141
+ ? {}
142
+ : { fileIds: named(room.fileIds, [...taken, ...targets]) }),
126
143
  changes: named(room.changes ?? {}, [...taken, ...targets]),
127
144
  binaryFiles: named(room.binaryFiles ?? {}, targets),
145
+ ...(opening && room.folders !== undefined ? { folders: room.folders } : {}),
128
146
  ...(next < paths.length ? { pageToken: taken.at(-1) } : {}),
129
147
  };
130
148
  }
131
- function advancement(room, from, at) {
149
+ function foldersMoved(room, from) {
150
+ const now = new Set(room.folders ?? []);
151
+ const before = new Set(from.folders ?? []);
152
+ const folders = [...now].filter((folder) => !before.has(folder)).sort();
153
+ const folderRemovals = [...before].filter((folder) => !now.has(folder)).sort();
154
+ return {
155
+ ...(folders.length === 0 ? {} : { folders }),
156
+ ...(folderRemovals.length === 0 ? {} : { folderRemovals }),
157
+ };
158
+ }
159
+ function advancement(room, from, at, whole = false) {
132
160
  const current = room.documents ?? {};
133
161
  const previous = from.documents ?? {};
134
162
  const currentTargets = new Set(Object.keys(room.binaryFiles ?? {}));
135
163
  const previousTargets = new Set(Object.keys(from.binaryFiles ?? {}));
136
164
  const headMoved = (path) => room.revisions?.[path] !== from.revisions?.[path] ||
165
+ room.fileIds?.[path] !== from.fileIds?.[path] ||
137
166
  !sameChange(room.changes?.[path], from.changes?.[path]);
138
167
  const changed = Object.keys(current)
139
168
  .filter((path) => current[path] !== previous[path] || headMoved(path))
140
169
  .sort();
141
- const size = pageSize ?? changed.length;
170
+ const size = whole ? changed.length : (pageSize ?? changed.length);
142
171
  const taken = changed.slice(at, at + size);
143
172
  const next = at + taken.length;
144
173
  const opening = at === 0;
@@ -155,8 +184,12 @@ export function createMemoryAdapter(options = {}) {
155
184
  ...(models === undefined ? {} : { models }),
156
185
  documents: named(current, taken),
157
186
  revisions: named(room.revisions ?? {}, [...taken, ...targets]),
187
+ ...(room.fileIds === undefined
188
+ ? {}
189
+ : { fileIds: named(room.fileIds, [...taken, ...targets]) }),
158
190
  changes: named(room.changes ?? {}, [...taken, ...targets]),
159
191
  binaryFiles: named(room.binaryFiles ?? {}, targets),
192
+ ...(opening ? foldersMoved(room, from) : {}),
160
193
  removals: opening
161
194
  ? [
162
195
  ...Object.keys(previous).filter((path) => !(path in current) && !currentTargets.has(path)),
@@ -166,6 +199,26 @@ export function createMemoryAdapter(options = {}) {
166
199
  ...(pageToken === undefined ? {} : { pageToken }),
167
200
  };
168
201
  }
202
+ function carry(locator) {
203
+ const at = carried.get(locator);
204
+ const current = rooms.find((entry) => entry.locator === locator);
205
+ if (at === undefined || current === undefined || current.roomVersion === at)
206
+ return;
207
+ const from = served.get(locator)?.get(at);
208
+ carried.set(locator, current.roomVersion);
209
+ if (from === undefined)
210
+ return;
211
+ const page = advancement(current, from, 0, true);
212
+ for (const observer of observers) {
213
+ observer({
214
+ kind: 'room-change',
215
+ locator,
216
+ from: at,
217
+ roomVersion: current.roomVersion,
218
+ read: async () => page,
219
+ });
220
+ }
221
+ }
169
222
  function pageOf(conversation, after) {
170
223
  const messages = conversation.messages ?? [];
171
224
  const first = typeof after === 'string' ? messages.findIndex((message) => message.position > after) : 0;
@@ -180,16 +233,13 @@ export function createMemoryAdapter(options = {}) {
180
233
  : {}),
181
234
  };
182
235
  }
183
- function historiesOf(input) {
184
- const asked = input?.conversations;
185
- if (!Array.isArray(asked))
186
- return { kind: 'rejected', code: 'invalid_request', fields: {}, retryable: false };
236
+ function historiesOf(request) {
187
237
  const histories = [];
188
- for (const entry of asked) {
189
- const conversation = typeof entry.conversation === 'string' ? scripted.get(entry.conversation) : undefined;
238
+ for (const entry of request.conversations) {
239
+ const conversation = scripted.get(entry.conversation);
190
240
  if (conversation === undefined)
191
241
  continue;
192
- const outcome = conversation.outcomes?.['conversation.history'];
242
+ const outcome = conversation.outcomes?.history;
193
243
  if (outcome !== undefined)
194
244
  return outcome;
195
245
  histories.push(pageOf(conversation, entry.after));
@@ -200,15 +250,13 @@ export function createMemoryAdapter(options = {}) {
200
250
  function conversationAction(request) {
201
251
  const name = request.name;
202
252
  const input = (request.input ?? {});
203
- if (name === 'conversation.history')
204
- return historiesOf(request.input);
205
- if (name === 'conversation.create') {
253
+ if (name === 'conversations/create') {
206
254
  issued += 1;
207
255
  const conversation = `conversation-${issued}`;
208
256
  scripted.set(conversation, { conversation });
209
- return { kind: 'action', result: { conversation } };
257
+ return { kind: 'action', result: { conversation_id: conversation } };
210
258
  }
211
- const named = typeof input.conversation === 'string' ? input.conversation : '';
259
+ const named = typeof input.conversation_id === 'string' ? input.conversation_id : '';
212
260
  const conversation = scripted.get(named);
213
261
  if (conversation === undefined) {
214
262
  return {
@@ -221,34 +269,28 @@ export function createMemoryAdapter(options = {}) {
221
269
  const outcome = conversation.outcomes?.[name];
222
270
  if (outcome !== undefined)
223
271
  return outcome;
224
- if (name === 'conversation.send') {
272
+ if (name === 'conversations/send') {
225
273
  const operation = typeof input.operation_id === 'string' ? input.operation_id : null;
226
274
  const answered = operation === null ? undefined : sentTurns.get(operation);
227
275
  if (answered !== undefined)
228
- return { kind: 'action', result: { turn: answered } };
276
+ return { kind: 'action', result: { message_id: answered } };
229
277
  issued += 1;
230
278
  const turn = `turn-${issued}`;
231
- unsealed.set(turn, named);
232
279
  if (operation !== null)
233
280
  sentTurns.set(operation, turn);
234
- return { kind: 'action', result: { turn } };
281
+ return { kind: 'action', result: { message_id: turn } };
235
282
  }
236
- if (name === 'conversation.cancel') {
237
- const turn = typeof input.turn === 'string' ? input.turn : '';
238
- if (sealed.has(turn) || !unsealed.has(turn)) {
239
- return { kind: 'rejected', code: 'turn-sealed', fields: { turn }, retryable: false };
240
- }
241
- unsealed.delete(turn);
242
- return { kind: 'action', result: { turn, cancelled: true } };
243
- }
244
- if (name === 'conversation.delete')
283
+ if (name === 'conversations/delete')
245
284
  scripted.delete(named);
246
- return { kind: 'action', result: { conversation: named } };
285
+ return { kind: 'action', result: { conversation_id: named } };
247
286
  }
248
287
  function announceList() {
249
- const entries = [...scripted.values()].flatMap(({ conversation, listed }) => listed === undefined ? [] : [{ conversation, ...listed }]);
250
- for (const listener of listeners)
251
- listener({ kind: 'list', entries });
288
+ const entries = [...scripted.values()].flatMap(({ conversation, listed, messages = [] }) => listed === undefined
289
+ ? []
290
+ : [{ conversation, ...listed, newest: messages.at(-1)?.position ?? null }]);
291
+ for (const listener of listeners) {
292
+ listener({ kind: 'list', entries, whole: true, more: false, cursor: null });
293
+ }
252
294
  }
253
295
  function discovery() {
254
296
  return {
@@ -260,8 +302,41 @@ export function createMemoryAdapter(options = {}) {
260
302
  ...(options.member === undefined ? {} : { member: options.member }),
261
303
  };
262
304
  }
305
+ function discovered() {
306
+ return resolveScript(scripts.discover, discovery, undefined).then((answer) => {
307
+ if (answer.kind === 'discovery')
308
+ for (const room of answer.rooms)
309
+ note(room.locator, room);
310
+ return answer;
311
+ });
312
+ }
313
+ function opened(notify) {
314
+ const opening = ++openings;
315
+ notify({ kind: 'opened' });
316
+ void discovered()
317
+ .catch(() => ({ kind: 'transport-uncertainty' }))
318
+ .then((answer) => {
319
+ if (opening !== openings)
320
+ return;
321
+ if (answer.kind !== 'discovery') {
322
+ openings += 1;
323
+ carried.clear();
324
+ const revoked = answer.kind === 'revoked-authorization';
325
+ notify({ kind: 'closed', revoked, lost: !revoked, authorityChanged: false });
326
+ return;
327
+ }
328
+ declaredRooms.clear();
329
+ for (const room of answer.rooms)
330
+ declaredRooms.add(room.locator);
331
+ notify({ kind: 'discovered', discovery: answer });
332
+ });
333
+ }
334
+ function emitAll(event) {
335
+ for (const observer of observers)
336
+ observer(event);
337
+ }
263
338
  return {
264
- ...(options.exhaustiveDiscovery === undefined ? {} : { exhaustiveDiscovery: true }),
339
+ ...(options.roomTypes === undefined ? {} : { roomTypes: options.roomTypes }),
265
340
  setRooms(next) {
266
341
  rooms = [...next];
267
342
  retain(rooms);
@@ -270,26 +345,37 @@ export function createMemoryAdapter(options = {}) {
270
345
  scriptConversations(next);
271
346
  },
272
347
  open() {
348
+ carried.clear();
273
349
  announceList();
274
- for (const observer of observers)
275
- observer({ kind: 'opened' });
350
+ opened(emitAll);
276
351
  },
277
- close(revoked = false) {
278
- for (const observer of observers)
279
- observer({ kind: 'closed', revoked });
352
+ close({ revoked = false, lost = !revoked, authorityChanged = false } = {}) {
353
+ openings += 1;
354
+ carried.clear();
355
+ emitAll({ kind: 'closed', revoked, lost, authorityChanged });
280
356
  },
281
357
  expire(locators) {
282
358
  expiring = [...locators];
283
359
  },
284
- notifyRoomVersion({ locator, roomVersion }) {
285
- for (const observer of observers)
286
- observer({ kind: 'room-version', locator, roomVersion });
360
+ notifyRoomVersion({ locator }) {
361
+ carry(locator);
362
+ },
363
+ notifyAuthorityChanged(locators) {
364
+ const named = new Set(locators ?? [...declaredRooms, ...rooms.map((room) => room.locator)]);
365
+ const listed = rooms.filter((room) => named.has(room.locator));
366
+ const left = [...named].filter((locator) => !listed.some((room) => room.locator === locator));
367
+ for (const room of listed)
368
+ declaredRooms.add(room.locator);
369
+ for (const locator of left)
370
+ declaredRooms.delete(locator);
371
+ emitAll({
372
+ kind: 'authority-changed',
373
+ rooms: listed.map(declared),
374
+ left,
375
+ ...(options.member === undefined ? {} : { member: options.member }),
376
+ });
287
377
  },
288
378
  notifyConversation(event) {
289
- if (event.kind === 'accepted-message') {
290
- sealed.add(event.message.turn);
291
- unsealed.delete(event.message.turn);
292
- }
293
379
  for (const listener of listeners)
294
380
  listener(event);
295
381
  },
@@ -298,24 +384,31 @@ export function createMemoryAdapter(options = {}) {
298
384
  observer(mode);
299
385
  },
300
386
  heldRooms() {
301
- return held;
302
- },
303
- heldDemand() {
304
- return heldDemand;
387
+ return [...carried.keys()].sort();
305
388
  },
306
389
  replicatedConversations() {
307
390
  return replicated;
308
391
  },
392
+ heldConversationCursor() {
393
+ return heldCursor;
394
+ },
309
395
  conversations: {
310
396
  replicate(next) {
311
397
  replicated = [...next];
312
398
  },
399
+ hold(cursor) {
400
+ heldCursor = cursor;
401
+ },
313
402
  observe(notify) {
314
403
  listeners.add(notify);
315
404
  return () => {
316
405
  listeners.delete(notify);
317
406
  };
318
407
  },
408
+ history: (request) => Promise.resolve(historiesOf(request)),
409
+ ...(options.agents === undefined
410
+ ? {}
411
+ : { agents: () => Promise.resolve(structuredClone(options.agents)) }),
319
412
  },
320
413
  serviceMode: {
321
414
  observe(notify) {
@@ -326,45 +419,46 @@ export function createMemoryAdapter(options = {}) {
326
419
  },
327
420
  },
328
421
  roomStream: {
329
- hold(asked, demand) {
330
- held = asked.map((room) => room.locator);
331
- heldDemand = demand;
332
- const expired = expiring.filter((locator) => held.includes(locator));
333
- expiring = [];
334
- const moved = asked.flatMap((room) => {
422
+ hold(asked) {
423
+ const named = asked.map((room) => room.locator);
424
+ const unknown = asked.flatMap((room) => {
335
425
  const current = rooms.find((entry) => entry.locator === room.locator);
336
426
  return current === undefined ||
337
- expired.includes(room.locator) ||
338
- current.roomVersion === room.roomVersion
427
+ current.roomVersion === room.roomVersion ||
428
+ served.get(room.locator)?.has(room.roomVersion) === true
339
429
  ? []
340
- : [{ locator: room.locator, roomVersion: current.roomVersion }];
430
+ : [room.locator];
341
431
  });
432
+ const expired = [
433
+ ...new Set([...expiring.filter((locator) => named.includes(locator)), ...unknown]),
434
+ ];
435
+ expiring = expiring.filter((locator) => !named.includes(locator));
342
436
  queueMicrotask(() => {
343
- for (const observer of observers) {
344
- for (const room of moved)
345
- observer({ kind: 'room-version', ...room });
346
- observer({ kind: 'opening', expired });
437
+ for (const room of asked) {
438
+ if (expired.includes(room.locator))
439
+ carried.delete(room.locator);
440
+ else {
441
+ carried.set(room.locator, room.roomVersion);
442
+ carry(room.locator);
443
+ }
347
444
  }
445
+ emitAll({ kind: 'caught-up', expired });
348
446
  });
349
447
  },
350
- reopen() {
351
- queueMicrotask(() => {
352
- for (const observer of observers)
353
- observer({ kind: 'closed', revoked: false });
354
- });
355
- },
356
- published() { },
357
448
  observe(notify) {
358
449
  observers.add(notify);
359
450
  queueMicrotask(() => {
360
451
  if (!observers.has(notify))
361
452
  return;
362
453
  if (options.unopened === true) {
363
- notify({ kind: 'closed', revoked: false });
454
+ notify({ kind: 'closed', revoked: false, lost: true, authorityChanged: false });
364
455
  return;
365
456
  }
366
457
  announceList();
367
- notify({ kind: 'opened' });
458
+ opened((event) => {
459
+ if (observers.has(notify))
460
+ notify(event);
461
+ });
368
462
  });
369
463
  return () => {
370
464
  observers.delete(notify);
@@ -381,12 +475,7 @@ export function createMemoryAdapter(options = {}) {
381
475
  return resolveScript(scripts.authorize, () => ({}), undefined);
382
476
  },
383
477
  async discover(_authorization) {
384
- return resolveScript(scripts.discover, discovery, undefined).then((answer) => {
385
- if (answer.kind === 'discovery')
386
- for (const room of answer.rooms)
387
- note(room.locator, room);
388
- return answer;
389
- });
478
+ return discovered();
390
479
  },
391
480
  reached,
392
481
  follows(locator, roomVersion, next) {
@@ -443,7 +532,13 @@ export function createMemoryAdapter(options = {}) {
443
532
  retryable: false,
444
533
  affected: {},
445
534
  };
446
- }, request).then((answer) => note(request.locator, answer));
535
+ }, request).then((answer) => {
536
+ const destination = request.changeBatch.between?.destination.room;
537
+ if (answer.kind === 'accepted' && answer.destination !== undefined && destination) {
538
+ note(destination, answer.destination);
539
+ }
540
+ return note(request.locator, answer);
541
+ });
447
542
  },
448
543
  async history(_authorization, request) {
449
544
  return resolveScript(scripts.history, () => {
@@ -459,31 +554,42 @@ export function createMemoryAdapter(options = {}) {
459
554
  },
460
555
  async binary(_authorization, request) {
461
556
  return resolveScript(scripts.binary, () => {
462
- if (request.kind === 'reference') {
463
- return { kind: 'rejected', code: 'unscripted-binary', fields: {}, retryable: false };
464
- }
465
557
  const room = rooms.find((entry) => entry.locator === request.locator);
466
558
  if (room === undefined)
467
559
  return { kind: 'room-not-readable', locator: request.locator };
468
- const blob = room.blobs?.[request.path];
560
+ const blob = heldBlob(room, request.path);
469
561
  return blob === undefined
470
562
  ? { kind: 'rejected', code: 'file_not_found', fields: {}, retryable: false }
471
- : { kind: 'binary', blob };
563
+ : { kind: 'binary', body: blob.stream(), mediaType: blob.type };
472
564
  }, request);
473
565
  },
474
- referrerScope(referrer) {
475
- if (typeof referrer !== 'object' || referrer === null)
476
- return null;
477
- const { room, path, message } = referrer;
478
- if (typeof message === 'string')
479
- return { kind: 'conversations' };
480
- if (typeof room !== 'string')
481
- return null;
482
- return { kind: 'room', locator: room, ...(typeof path === 'string' ? { path } : {}) };
566
+ async link(_authorization, request) {
567
+ return resolveScript(scripts.link, () => {
568
+ const room = rooms.find((entry) => entry.locator === request.locator);
569
+ if (room === undefined)
570
+ return { kind: 'room-not-readable', locator: request.locator };
571
+ const blob = heldBlob(room, request.path);
572
+ if (blob === undefined) {
573
+ return { kind: 'rejected', code: 'file_not_found', fields: {}, retryable: false };
574
+ }
575
+ const url = linked.get(blob) ?? URL.createObjectURL(blob);
576
+ linked.set(blob, url);
577
+ return { kind: 'link', url, name: lastName(request.path), mediaType: blob.type };
578
+ }, request);
579
+ },
580
+ async attach(_authorization, request) {
581
+ return resolveScript(scripts.attach, () => {
582
+ const room = rooms.find((entry) => entry.locator === request.locator);
583
+ if (room === undefined)
584
+ return { kind: 'room-not-readable', locator: request.locator };
585
+ const path = freePath(room, request.name);
586
+ attached.set(`${room.locator}\u0000${path}`, request.blob);
587
+ return { kind: 'attached', path, revision: `attached-${attached.size}` };
588
+ }, request);
483
589
  },
484
590
  async action(_authorization, request) {
485
591
  return resolveScript(scripts.action, () => {
486
- if (request.name.startsWith('conversation.'))
592
+ if (request.name.startsWith('conversations/'))
487
593
  return conversationAction(request);
488
594
  return { kind: 'rejected', code: 'unscripted-action', fields: {}, retryable: false };
489
595
  }, request).then((answer) => {
@@ -0,0 +1,28 @@
1
+ import type { AcceptedMessage } from './adapter.js';
2
+ import type { Connection } from './tables.js';
3
+ export declare const MESSAGE_TABLES: readonly string[];
4
+ export type HeldConversation = {
5
+ readonly conversation: string;
6
+ readonly read: string | null | undefined;
7
+ readonly partial: boolean;
8
+ };
9
+ export type MessageWindow = {
10
+ readonly messages: AcceptedMessage[];
11
+ readonly more: boolean;
12
+ };
13
+ export type ConversationWrite = {
14
+ readonly conversation: string;
15
+ readonly messages: readonly AcceptedMessage[];
16
+ readonly read?: {
17
+ readonly at: string | null;
18
+ readonly partial: boolean;
19
+ };
20
+ };
21
+ export declare function heldConversations(connection: Connection): HeldConversation[];
22
+ export declare function keptMessages(connection: Connection, { conversation, messages, read }: ConversationWrite): null;
23
+ export declare function messageWindow(connection: Connection, conversation: string, limit: number, before: readonly [string, string] | null, phrase: string | null): MessageWindow;
24
+ export declare function messageCount(connection: Connection, conversation: string, phrase: string | null): number;
25
+ export declare function turnArrived(connection: Connection, conversation: string, turn: string): boolean;
26
+ export declare function droppedMessages(connection: Connection, conversations: readonly string[]): void;
27
+ export declare function forgottenMessages(connection: Connection): void;
28
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,aAAa,CAAC;AAE1D,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAM3C,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,CAAC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAE7F,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IAC9C,QAAQ,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;CAC3E,CAAC;AAMF,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,gBAAgB,EAAE,CAmB5E;AAED,wBAAgB,YAAY,CAC1B,UAAU,EAAE,UAAU,EACtB,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,iBAAiB,GAClD,IAAI,CA+BN;AAkBD,wBAAgB,aAAa,CAC3B,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,EACxC,MAAM,EAAE,MAAM,GAAG,IAAI,GACpB,aAAa,CAWf;AAED,wBAAgB,YAAY,CAC1B,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GAAG,IAAI,GACpB,MAAM,CAOR;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAO/F;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAK9F;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAG9D"}
@@ -0,0 +1,89 @@
1
+ import { HOLDS_PHRASE } from './phrase.js';
2
+ export const MESSAGE_TABLES = [
3
+ 'CREATE TABLE conversation_reads (conversation TEXT PRIMARY KEY, read TEXT, partial INTEGER NOT NULL) WITHOUT ROWID',
4
+ 'CREATE TABLE conversation_messages (id INTEGER PRIMARY KEY, conversation TEXT NOT NULL, identity TEXT NOT NULL, turn TEXT NOT NULL, position TEXT NOT NULL, message TEXT NOT NULL, text TEXT NOT NULL)',
5
+ 'CREATE UNIQUE INDEX conversation_messages_identity ON conversation_messages (conversation, identity)',
6
+ 'CREATE INDEX conversation_messages_position ON conversation_messages (conversation, position, identity)',
7
+ 'CREATE INDEX conversation_messages_turn ON conversation_messages (conversation, turn)',
8
+ ];
9
+ function text(value) {
10
+ return typeof value === 'string' ? value : String(value);
11
+ }
12
+ export function heldConversations(connection) {
13
+ const held = new Map();
14
+ for (const row of connection.rows('SELECT conversation, read, partial FROM conversation_reads')) {
15
+ held.set(text(row.conversation), {
16
+ conversation: text(row.conversation),
17
+ read: row.read === null ? null : text(row.read),
18
+ partial: Number(row.partial) === 1,
19
+ });
20
+ }
21
+ for (const row of connection.rows('SELECT DISTINCT conversation FROM conversation_messages WHERE conversation NOT IN (SELECT conversation FROM conversation_reads)')) {
22
+ held.set(text(row.conversation), {
23
+ conversation: text(row.conversation),
24
+ read: undefined,
25
+ partial: true,
26
+ });
27
+ }
28
+ return [...held.values()];
29
+ }
30
+ export function keptMessages(connection, { conversation, messages, read }) {
31
+ for (const message of messages) {
32
+ const [held] = connection.rows('SELECT id FROM conversation_messages WHERE conversation = ? AND identity = ?', [conversation, message.identity]);
33
+ const record = [
34
+ message.turn,
35
+ message.position,
36
+ JSON.stringify(message.message ?? null),
37
+ message.text,
38
+ ];
39
+ if (held === undefined) {
40
+ connection.run('INSERT INTO conversation_messages (conversation, identity, turn, position, message, text) VALUES (?, ?, ?, ?, ?, ?)', [conversation, message.identity, ...record]);
41
+ }
42
+ else {
43
+ connection.run('UPDATE conversation_messages SET turn = ?, position = ?, message = ?, text = ? WHERE id = ?', [...record, held.id]);
44
+ }
45
+ }
46
+ if (read !== undefined) {
47
+ connection.run('INSERT OR REPLACE INTO conversation_reads (conversation, read, partial) VALUES (?, ?, ?)', [conversation, read.at, read.partial ? 1 : 0]);
48
+ }
49
+ return null;
50
+ }
51
+ function accepted(row) {
52
+ return {
53
+ identity: text(row.identity),
54
+ turn: text(row.turn),
55
+ position: text(row.position),
56
+ text: text(row.text),
57
+ message: JSON.parse(text(row.message)),
58
+ };
59
+ }
60
+ function holding(phrase) {
61
+ return phrase === null
62
+ ? { sql: '', bind: [] }
63
+ : { sql: ` AND ${HOLDS_PHRASE}(text, ?)`, bind: [phrase.toLowerCase()] };
64
+ }
65
+ export function messageWindow(connection, conversation, limit, before, phrase) {
66
+ const held = holding(phrase);
67
+ const rows = connection.rows(`SELECT identity, turn, position, message, text FROM conversation_messages WHERE conversation = ?${held.sql}${before === null ? '' : ' AND (position < ? OR (position = ? AND identity < ?))'} ORDER BY position DESC, identity DESC LIMIT ?`, before === null
68
+ ? [conversation, ...held.bind, limit + 1]
69
+ : [conversation, ...held.bind, before[0], before[0], before[1], limit + 1]);
70
+ return { messages: rows.slice(0, limit).map(accepted).reverse(), more: rows.length > limit };
71
+ }
72
+ export function messageCount(connection, conversation, phrase) {
73
+ const held = holding(phrase);
74
+ const [counted] = connection.rows(`SELECT count(*) AS held FROM conversation_messages WHERE conversation = ?${held.sql}`, [conversation, ...held.bind]);
75
+ return Number(counted?.held ?? 0);
76
+ }
77
+ export function turnArrived(connection, conversation, turn) {
78
+ return (connection.rows('SELECT 1 FROM conversation_messages WHERE conversation = ? AND turn = ? LIMIT 1', [conversation, turn]).length > 0);
79
+ }
80
+ export function droppedMessages(connection, conversations) {
81
+ for (const conversation of conversations) {
82
+ connection.run('DELETE FROM conversation_messages WHERE conversation = ?', [conversation]);
83
+ connection.run('DELETE FROM conversation_reads WHERE conversation = ?', [conversation]);
84
+ }
85
+ }
86
+ export function forgottenMessages(connection) {
87
+ connection.run('DELETE FROM conversation_messages');
88
+ connection.run('DELETE FROM conversation_reads');
89
+ }
package/dist/model.d.ts CHANGED
@@ -28,6 +28,11 @@ export type ModelState = {
28
28
  verifiedWith: ModelIdentity;
29
29
  types: VerifiedModel['types'];
30
30
  };
31
+ export type StoredModel = Extract<ModelState, {
32
+ status: 'unavailable';
33
+ }> | Omit<Extract<ModelState, {
34
+ status: 'current' | 'cached';
35
+ }>, 'types'>;
31
36
  export type TypesResult = {
32
37
  status: Open<'unavailable'>;
33
38
  reason: ModelUnavailableReason;
@@ -51,8 +56,8 @@ export type ValidationReceipt = {
51
56
  export declare const NO_MODEL: ModelState;
52
57
  export declare function definitionOf(state: ModelState, type: string | undefined): Definition | undefined;
53
58
  export declare function modelTypes(state: ModelState): TypesResult;
59
+ export declare function storedModel(state: ModelState): StoredModel;
54
60
  export declare function cached(state: ModelState): ModelState;
55
61
  export declare function sameModel(left: ModelIdentity, right: ModelIdentity): boolean;
56
- export declare function sameTyping(left: ModelState, right: ModelState): boolean;
57
62
  export declare function referenceField(facts: ValueFacts | undefined): boolean;
58
63
  //# sourceMappingURL=model.d.ts.map