@agentshouse/mdruntime 0.1.0-c2e8a6f99866d → 0.1.0-c2ef9303a058d

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 (220) hide show
  1. package/README.md +11 -9
  2. package/dist/adapter.d.ts +95 -67
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/attached-model.d.ts +5 -11
  5. package/dist/attached-model.d.ts.map +1 -1
  6. package/dist/attached-model.js +36 -47
  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 +17 -9
  11. package/dist/bootstrap.d.ts.map +1 -1
  12. package/dist/bootstrap.js +143 -94
  13. package/dist/chain.d.ts +2 -1
  14. package/dist/chain.d.ts.map +1 -1
  15. package/dist/change-set.d.ts +1 -7
  16. package/dist/change-set.d.ts.map +1 -1
  17. package/dist/change-set.js +1 -4
  18. package/dist/client.d.ts +15 -12
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +56 -8
  21. package/dist/collection.d.ts +7 -4
  22. package/dist/collection.d.ts.map +1 -1
  23. package/dist/collection.js +24 -19
  24. package/dist/compile.d.ts.map +1 -1
  25. package/dist/compile.js +117 -243
  26. package/dist/connection.d.ts +14 -12
  27. package/dist/connection.d.ts.map +1 -1
  28. package/dist/connection.js +1466 -1431
  29. package/dist/context.d.ts +2 -0
  30. package/dist/context.d.ts.map +1 -0
  31. package/dist/{chat/context.js → context.js} +1 -4
  32. package/dist/conversation.d.ts +18 -15
  33. package/dist/conversation.d.ts.map +1 -1
  34. package/dist/conversation.js +273 -185
  35. package/dist/durability.d.ts +6 -4
  36. package/dist/durability.d.ts.map +1 -1
  37. package/dist/durability.js +5 -0
  38. package/dist/entry.d.ts +3 -2
  39. package/dist/entry.d.ts.map +1 -1
  40. package/dist/entry.js +17 -8
  41. package/dist/file.d.ts +2 -11
  42. package/dist/file.d.ts.map +1 -1
  43. package/dist/file.js +6 -4
  44. package/dist/host.d.ts +3 -7
  45. package/dist/host.d.ts.map +1 -1
  46. package/dist/host.js +55 -38
  47. package/dist/identity.d.ts +0 -2
  48. package/dist/identity.d.ts.map +1 -1
  49. package/dist/identity.js +0 -6
  50. package/dist/index.d.ts +15 -12
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +6 -3
  53. package/dist/job.d.ts +14 -0
  54. package/dist/job.d.ts.map +1 -0
  55. package/dist/job.js +28 -0
  56. package/dist/live.d.ts +2 -1
  57. package/dist/live.d.ts.map +1 -1
  58. package/dist/live.js +9 -5
  59. package/dist/media.d.ts +8 -0
  60. package/dist/media.d.ts.map +1 -0
  61. package/dist/media.js +218 -0
  62. package/dist/memory.d.ts +12 -6
  63. package/dist/memory.d.ts.map +1 -1
  64. package/dist/memory.js +209 -101
  65. package/dist/messages.d.ts +28 -0
  66. package/dist/messages.d.ts.map +1 -0
  67. package/dist/messages.js +89 -0
  68. package/dist/model.d.ts +8 -8
  69. package/dist/model.d.ts.map +1 -1
  70. package/dist/model.js +7 -13
  71. package/dist/observation.d.ts +3 -2
  72. package/dist/observation.d.ts.map +1 -1
  73. package/dist/observation.js +8 -3
  74. package/dist/outcome.d.ts +13 -6
  75. package/dist/outcome.d.ts.map +1 -1
  76. package/dist/outcome.js +16 -5
  77. package/dist/overlay.js +1 -1
  78. package/dist/paths.d.ts +1 -0
  79. package/dist/paths.d.ts.map +1 -1
  80. package/dist/paths.js +4 -0
  81. package/dist/query.d.ts +10 -206
  82. package/dist/query.d.ts.map +1 -1
  83. package/dist/query.js +3 -494
  84. package/dist/reach.d.ts +14 -9
  85. package/dist/reach.d.ts.map +1 -1
  86. package/dist/reach.js +84 -72
  87. package/dist/react.d.ts +3 -1
  88. package/dist/react.d.ts.map +1 -1
  89. package/dist/readiness.d.ts +14 -17
  90. package/dist/readiness.d.ts.map +1 -1
  91. package/dist/readiness.js +2 -8
  92. package/dist/recovery.d.ts +1 -0
  93. package/dist/recovery.d.ts.map +1 -1
  94. package/dist/recovery.js +26 -0
  95. package/dist/refresh.d.ts +10 -4
  96. package/dist/refresh.d.ts.map +1 -1
  97. package/dist/refresh.js +88 -30
  98. package/dist/replica.d.ts +11 -8
  99. package/dist/replica.d.ts.map +1 -1
  100. package/dist/replica.js +30 -15
  101. package/dist/room-draft.d.ts +22 -5
  102. package/dist/room-draft.d.ts.map +1 -1
  103. package/dist/room-draft.js +131 -51
  104. package/dist/rows.d.ts +18 -16
  105. package/dist/rows.d.ts.map +1 -1
  106. package/dist/rows.js +45 -153
  107. package/dist/save.d.ts +2 -0
  108. package/dist/save.d.ts.map +1 -1
  109. package/dist/save.js +13 -7
  110. package/dist/search.d.ts +12 -95
  111. package/dist/search.d.ts.map +1 -1
  112. package/dist/search.js +10 -349
  113. package/dist/shared.d.ts +0 -1
  114. package/dist/shared.d.ts.map +1 -1
  115. package/dist/shared.js +0 -10
  116. package/dist/status.d.ts +4 -3
  117. package/dist/status.d.ts.map +1 -1
  118. package/dist/status.js +3 -1
  119. package/dist/store.d.ts +25 -62
  120. package/dist/store.d.ts.map +1 -1
  121. package/dist/store.js +43 -282
  122. package/dist/stored.d.ts +12 -26
  123. package/dist/stored.d.ts.map +1 -1
  124. package/dist/stored.js +189 -226
  125. package/dist/tables.d.ts +55 -48
  126. package/dist/tables.d.ts.map +1 -1
  127. package/dist/tables.js +107 -57
  128. package/dist/transfer.d.ts +0 -5
  129. package/dist/transfer.d.ts.map +1 -1
  130. package/dist/validation.d.ts +3 -4
  131. package/dist/validation.d.ts.map +1 -1
  132. package/dist/validation.js +39 -97
  133. package/dist/windows.d.ts +1 -12
  134. package/dist/windows.d.ts.map +1 -1
  135. package/dist/windows.js +2 -75
  136. package/dist/wire.d.ts +14 -9
  137. package/dist/wire.d.ts.map +1 -1
  138. package/dist/wire.js +5 -3
  139. package/package.json +14 -18
  140. package/AGENTS.md +0 -312
  141. package/dist/chat/acts.d.ts +0 -33
  142. package/dist/chat/acts.d.ts.map +0 -1
  143. package/dist/chat/acts.js +0 -36
  144. package/dist/chat/context.d.ts +0 -3
  145. package/dist/chat/context.d.ts.map +0 -1
  146. package/dist/chat/earlier.d.ts +0 -10
  147. package/dist/chat/earlier.d.ts.map +0 -1
  148. package/dist/chat/earlier.js +0 -54
  149. package/dist/chat/house.d.ts +0 -25
  150. package/dist/chat/house.d.ts.map +0 -1
  151. package/dist/chat/house.js +0 -38
  152. package/dist/chat/input.d.ts +0 -11
  153. package/dist/chat/input.d.ts.map +0 -1
  154. package/dist/chat/input.js +0 -92
  155. package/dist/chat/kit.d.ts +0 -61
  156. package/dist/chat/kit.d.ts.map +0 -1
  157. package/dist/chat/kit.js +0 -123
  158. package/dist/chat/lasting.d.ts +0 -6
  159. package/dist/chat/lasting.d.ts.map +0 -1
  160. package/dist/chat/lasting.js +0 -19
  161. package/dist/chat/list.d.ts +0 -28
  162. package/dist/chat/list.d.ts.map +0 -1
  163. package/dist/chat/list.js +0 -103
  164. package/dist/chat/markdown.d.ts +0 -21
  165. package/dist/chat/markdown.d.ts.map +0 -1
  166. package/dist/chat/markdown.js +0 -246
  167. package/dist/chat/message.d.ts +0 -7
  168. package/dist/chat/message.d.ts.map +0 -1
  169. package/dist/chat/message.js +0 -244
  170. package/dist/chat/messages.d.ts +0 -30
  171. package/dist/chat/messages.d.ts.map +0 -1
  172. package/dist/chat/messages.js +0 -198
  173. package/dist/chat/name.d.ts +0 -7
  174. package/dist/chat/name.d.ts.map +0 -1
  175. package/dist/chat/name.js +0 -37
  176. package/dist/chat/opened.d.ts +0 -12
  177. package/dist/chat/opened.d.ts.map +0 -1
  178. package/dist/chat/opened.js +0 -19
  179. package/dist/chat/overlay.d.ts +0 -32
  180. package/dist/chat/overlay.d.ts.map +0 -1
  181. package/dist/chat/overlay.js +0 -103
  182. package/dist/chat/parts.d.ts +0 -58
  183. package/dist/chat/parts.d.ts.map +0 -1
  184. package/dist/chat/parts.js +0 -120
  185. package/dist/chat/pieces.d.ts +0 -62
  186. package/dist/chat/pieces.d.ts.map +0 -1
  187. package/dist/chat/pieces.js +0 -1110
  188. package/dist/chat/process.d.ts +0 -20
  189. package/dist/chat/process.d.ts.map +0 -1
  190. package/dist/chat/process.js +0 -32
  191. package/dist/chat/provider.d.ts +0 -70
  192. package/dist/chat/provider.d.ts.map +0 -1
  193. package/dist/chat/provider.js +0 -635
  194. package/dist/chat/question.d.ts +0 -9
  195. package/dist/chat/question.d.ts.map +0 -1
  196. package/dist/chat/question.js +0 -69
  197. package/dist/chat/questions.d.ts +0 -31
  198. package/dist/chat/questions.d.ts.map +0 -1
  199. package/dist/chat/questions.js +0 -111
  200. package/dist/chat/read.d.ts +0 -5
  201. package/dist/chat/read.d.ts.map +0 -1
  202. package/dist/chat/read.js +0 -13
  203. package/dist/chat/ready.d.ts +0 -37
  204. package/dist/chat/ready.d.ts.map +0 -1
  205. package/dist/chat/ready.js +0 -93
  206. package/dist/chat/room-file.d.ts +0 -8
  207. package/dist/chat/room-file.d.ts.map +0 -1
  208. package/dist/chat/room-file.js +0 -72
  209. package/dist/chat/steady.d.ts +0 -3
  210. package/dist/chat/steady.d.ts.map +0 -1
  211. package/dist/chat/steady.js +0 -26
  212. package/dist/chat/style.d.ts +0 -4
  213. package/dist/chat/style.d.ts.map +0 -1
  214. package/dist/chat/style.js +0 -306
  215. package/dist/chat.d.ts +0 -15
  216. package/dist/chat.d.ts.map +0 -1
  217. package/dist/chat.js +0 -8
  218. package/dist/reads.d.ts +0 -11
  219. package/dist/reads.d.ts.map +0 -1
  220. package/dist/reads.js +0 -98
package/dist/memory.js CHANGED
@@ -1,7 +1,12 @@
1
+ import { documentFacts } from '@agentshouse/mdmodel';
2
+ import { NO_ROOM_TYPES, schemaTypes } from './attached-model.js';
1
3
  import { sameChange } from './overlay.js';
4
+ const ATTACHMENTS = 'attachments';
5
+ function lastName(path) {
6
+ return path.slice(path.lastIndexOf('/') + 1);
7
+ }
2
8
  const FIXTURE_RETENTION = {
3
9
  history: { kind: 'duration', seconds: 1 },
4
- outcome: 604_800,
5
10
  refreshHorizon: 1,
6
11
  };
7
12
  function declaredRetention(value) {
@@ -15,12 +20,7 @@ function declaredRetention(value) {
15
20
  }
16
21
  const MODEL_ABSENT = { kind: 'model-unavailable', reason: 'not-readable' };
17
22
  function modelsOf(room) {
18
- return {
19
- collection: room.model ?? MODEL_ABSENT,
20
- ...(room.typedSelection === undefined
21
- ? {}
22
- : { selection: room.selectionModel ?? MODEL_ABSENT }),
23
- };
23
+ return { collection: room.model ?? MODEL_ABSENT };
24
24
  }
25
25
  function changedModels(room, from) {
26
26
  const now = modelsOf(room);
@@ -53,13 +53,16 @@ export function createMemoryAdapter(options = {}) {
53
53
  const listeners = new Set();
54
54
  const modeObservers = new Set();
55
55
  const scripted = new Map();
56
- const unsealed = new Map();
57
- const sealed = new Set();
58
56
  const sentTurns = new Map();
59
57
  const historyPageSize = options.historyPageSize;
60
- let held = [];
58
+ const attached = new Map();
59
+ const linked = new WeakMap();
60
+ const carried = new Map();
61
61
  let expiring = [];
62
+ let openings = 0;
63
+ const declaredRooms = new Set();
62
64
  let replicated = [];
65
+ let heldCursor = null;
63
66
  let issued = 0;
64
67
  function scriptConversations(entries) {
65
68
  scripted.clear();
@@ -87,8 +90,48 @@ export function createMemoryAdapter(options = {}) {
87
90
  const wanted = ordinals.get(`${locator}\u0000${target}`);
88
91
  return held !== undefined && wanted !== undefined && held >= wanted;
89
92
  }
93
+ function heldBlob(room, path) {
94
+ return attached.get(`${room.locator}\u0000${path}`) ?? room.blobs?.[path];
95
+ }
96
+ function freePath(room, name) {
97
+ const dot = name.lastIndexOf('.');
98
+ const stem = dot > 0 ? name.slice(0, dot) : name;
99
+ const extension = dot > 0 ? name.slice(dot) : '';
100
+ for (let count = 1;; count += 1) {
101
+ const path = `${ATTACHMENTS}/${count === 1 ? stem : `${stem}-${count}`}${extension}`;
102
+ if (heldBlob(room, path) === undefined && room.documents?.[path] === undefined)
103
+ return path;
104
+ }
105
+ }
106
+ const saved = new WeakMap();
107
+ function factsAt(room, path, content) {
108
+ const types = schemaTypes(room.model?.kind === 'model' ? room.model.snapshot.schema : null, NO_ROOM_TYPES);
109
+ let held = saved.get(types);
110
+ if (held === undefined) {
111
+ held = new Map();
112
+ saved.set(types, held);
113
+ }
114
+ const key = `${path}\u0000${content}`;
115
+ if (!held.has(key)) {
116
+ const facts = documentFacts(path, content, { types });
117
+ held.set(key, facts === null ? null : JSON.parse(JSON.stringify(facts)));
118
+ }
119
+ return held.get(key);
120
+ }
121
+ function factsIn(room, paths) {
122
+ const facts = {};
123
+ for (const path of paths) {
124
+ const content = room.documents?.[path];
125
+ const held = room.facts?.[path] ?? (content === undefined ? null : factsAt(room, path, content));
126
+ if (held !== null)
127
+ facts[path] = held;
128
+ }
129
+ return facts;
130
+ }
90
131
  function retain(entries) {
91
132
  for (const room of entries) {
133
+ for (const [path, content] of Object.entries(room.documents ?? {}))
134
+ factsAt(room, path, content);
92
135
  note(room.locator, room);
93
136
  const versions = served.get(room.locator) ?? new Map();
94
137
  versions.set(room.roomVersion, room);
@@ -121,6 +164,7 @@ export function createMemoryAdapter(options = {}) {
121
164
  roomVersion: room.roomVersion,
122
165
  ...(opening ? { models: modelsOf(room) } : {}),
123
166
  documents: named(room.documents ?? {}, taken),
167
+ facts: factsIn(room, taken),
124
168
  revisions: named(room.revisions ?? {}, [...taken, ...targets]),
125
169
  ...(room.fileIds === undefined
126
170
  ? {}
@@ -141,7 +185,7 @@ export function createMemoryAdapter(options = {}) {
141
185
  ...(folderRemovals.length === 0 ? {} : { folderRemovals }),
142
186
  };
143
187
  }
144
- function advancement(room, from, at) {
188
+ function advancement(room, from, at, whole = false) {
145
189
  const current = room.documents ?? {};
146
190
  const previous = from.documents ?? {};
147
191
  const currentTargets = new Set(Object.keys(room.binaryFiles ?? {}));
@@ -152,7 +196,7 @@ export function createMemoryAdapter(options = {}) {
152
196
  const changed = Object.keys(current)
153
197
  .filter((path) => current[path] !== previous[path] || headMoved(path))
154
198
  .sort();
155
- const size = pageSize ?? changed.length;
199
+ const size = whole ? changed.length : (pageSize ?? changed.length);
156
200
  const taken = changed.slice(at, at + size);
157
201
  const next = at + taken.length;
158
202
  const opening = at === 0;
@@ -168,6 +212,7 @@ export function createMemoryAdapter(options = {}) {
168
212
  roomVersion: room.roomVersion,
169
213
  ...(models === undefined ? {} : { models }),
170
214
  documents: named(current, taken),
215
+ facts: factsIn(room, taken),
171
216
  revisions: named(room.revisions ?? {}, [...taken, ...targets]),
172
217
  ...(room.fileIds === undefined
173
218
  ? {}
@@ -184,6 +229,26 @@ export function createMemoryAdapter(options = {}) {
184
229
  ...(pageToken === undefined ? {} : { pageToken }),
185
230
  };
186
231
  }
232
+ function carry(locator) {
233
+ const at = carried.get(locator);
234
+ const current = rooms.find((entry) => entry.locator === locator);
235
+ if (at === undefined || current === undefined || current.roomVersion === at)
236
+ return;
237
+ const from = served.get(locator)?.get(at);
238
+ carried.set(locator, current.roomVersion);
239
+ if (from === undefined)
240
+ return;
241
+ const page = advancement(current, from, 0, true);
242
+ for (const observer of observers) {
243
+ observer({
244
+ kind: 'room-change',
245
+ locator,
246
+ from: at,
247
+ roomVersion: current.roomVersion,
248
+ read: async () => page,
249
+ });
250
+ }
251
+ }
187
252
  function pageOf(conversation, after) {
188
253
  const messages = conversation.messages ?? [];
189
254
  const first = typeof after === 'string' ? messages.findIndex((message) => message.position > after) : 0;
@@ -198,16 +263,13 @@ export function createMemoryAdapter(options = {}) {
198
263
  : {}),
199
264
  };
200
265
  }
201
- function historiesOf(input) {
202
- const asked = input?.conversations;
203
- if (!Array.isArray(asked))
204
- return { kind: 'rejected', code: 'invalid_request', fields: {}, retryable: false };
266
+ function historiesOf(request) {
205
267
  const histories = [];
206
- for (const entry of asked) {
207
- const conversation = typeof entry.conversation === 'string' ? scripted.get(entry.conversation) : undefined;
268
+ for (const entry of request.conversations) {
269
+ const conversation = scripted.get(entry.conversation);
208
270
  if (conversation === undefined)
209
271
  continue;
210
- const outcome = conversation.outcomes?.['conversation.history'];
272
+ const outcome = conversation.outcomes?.history;
211
273
  if (outcome !== undefined)
212
274
  return outcome;
213
275
  histories.push(pageOf(conversation, entry.after));
@@ -218,15 +280,13 @@ export function createMemoryAdapter(options = {}) {
218
280
  function conversationAction(request) {
219
281
  const name = request.name;
220
282
  const input = (request.input ?? {});
221
- if (name === 'conversation.history')
222
- return historiesOf(request.input);
223
- if (name === 'conversation.create') {
283
+ if (name === 'conversations/create') {
224
284
  issued += 1;
225
285
  const conversation = `conversation-${issued}`;
226
286
  scripted.set(conversation, { conversation });
227
- return { kind: 'action', result: { conversation } };
287
+ return { kind: 'action', result: { conversation_id: conversation } };
228
288
  }
229
- const named = typeof input.conversation === 'string' ? input.conversation : '';
289
+ const named = typeof input.conversation_id === 'string' ? input.conversation_id : '';
230
290
  const conversation = scripted.get(named);
231
291
  if (conversation === undefined) {
232
292
  return {
@@ -239,34 +299,28 @@ export function createMemoryAdapter(options = {}) {
239
299
  const outcome = conversation.outcomes?.[name];
240
300
  if (outcome !== undefined)
241
301
  return outcome;
242
- if (name === 'conversation.send') {
302
+ if (name === 'conversations/send') {
243
303
  const operation = typeof input.operation_id === 'string' ? input.operation_id : null;
244
304
  const answered = operation === null ? undefined : sentTurns.get(operation);
245
305
  if (answered !== undefined)
246
- return { kind: 'action', result: { turn: answered } };
306
+ return { kind: 'action', result: { message_id: answered } };
247
307
  issued += 1;
248
308
  const turn = `turn-${issued}`;
249
- unsealed.set(turn, named);
250
309
  if (operation !== null)
251
310
  sentTurns.set(operation, turn);
252
- return { kind: 'action', result: { turn } };
311
+ return { kind: 'action', result: { message_id: turn } };
253
312
  }
254
- if (name === 'conversation.cancel') {
255
- const turn = typeof input.turn === 'string' ? input.turn : '';
256
- if (sealed.has(turn) || !unsealed.has(turn)) {
257
- return { kind: 'rejected', code: 'turn-sealed', fields: { turn }, retryable: false };
258
- }
259
- unsealed.delete(turn);
260
- return { kind: 'action', result: { turn, cancelled: true } };
261
- }
262
- if (name === 'conversation.delete')
313
+ if (name === 'conversations/delete')
263
314
  scripted.delete(named);
264
- return { kind: 'action', result: { conversation: named } };
315
+ return { kind: 'action', result: { conversation_id: named } };
265
316
  }
266
317
  function announceList() {
267
- const entries = [...scripted.values()].flatMap(({ conversation, listed }) => listed === undefined ? [] : [{ conversation, ...listed }]);
268
- for (const listener of listeners)
269
- listener({ kind: 'list', entries });
318
+ const entries = [...scripted.values()].flatMap(({ conversation, listed, messages = [] }) => listed === undefined
319
+ ? []
320
+ : [{ conversation, ...listed, newest: messages.at(-1)?.position ?? null }]);
321
+ for (const listener of listeners) {
322
+ listener({ kind: 'list', entries, whole: true, more: false, cursor: null });
323
+ }
270
324
  }
271
325
  function discovery() {
272
326
  return {
@@ -278,8 +332,42 @@ export function createMemoryAdapter(options = {}) {
278
332
  ...(options.member === undefined ? {} : { member: options.member }),
279
333
  };
280
334
  }
335
+ function discovered() {
336
+ return resolveScript(scripts.discover, discovery, undefined).then((answer) => {
337
+ if (answer.kind === 'discovery')
338
+ for (const room of answer.rooms)
339
+ note(room.locator, room);
340
+ return answer;
341
+ });
342
+ }
343
+ function opened(notify) {
344
+ const opening = ++openings;
345
+ notify({ kind: 'opened' });
346
+ void discovered()
347
+ .catch(() => ({ kind: 'transport-uncertainty' }))
348
+ .then((answer) => {
349
+ if (opening !== openings)
350
+ return;
351
+ if (answer.kind !== 'discovery') {
352
+ openings += 1;
353
+ carried.clear();
354
+ const revoked = answer.kind === 'revoked-authorization';
355
+ notify({ kind: 'closed', revoked, lost: !revoked, authorityChanged: false });
356
+ return;
357
+ }
358
+ declaredRooms.clear();
359
+ for (const room of answer.rooms)
360
+ declaredRooms.add(room.locator);
361
+ notify({ kind: 'discovered', discovery: answer });
362
+ });
363
+ }
364
+ function emitAll(event) {
365
+ for (const observer of observers)
366
+ observer(event);
367
+ }
281
368
  return {
282
- ...(options.exhaustiveDiscovery === undefined ? {} : { exhaustiveDiscovery: true }),
369
+ ...(options.roomTypes === undefined ? {} : { roomTypes: options.roomTypes }),
370
+ ...(options.typedPaths === undefined ? {} : { typedPaths: options.typedPaths }),
283
371
  setRooms(next) {
284
372
  rooms = [...next];
285
373
  retain(rooms);
@@ -288,33 +376,37 @@ export function createMemoryAdapter(options = {}) {
288
376
  scriptConversations(next);
289
377
  },
290
378
  open() {
379
+ carried.clear();
291
380
  announceList();
292
- for (const observer of observers)
293
- observer({ kind: 'opened' });
381
+ opened(emitAll);
294
382
  },
295
- close({ revoked = false, lost = !revoked } = {}) {
296
- for (const observer of observers)
297
- observer({ kind: 'closed', revoked, lost });
383
+ close({ revoked = false, lost = !revoked, authorityChanged = false } = {}) {
384
+ openings += 1;
385
+ carried.clear();
386
+ emitAll({ kind: 'closed', revoked, lost, authorityChanged });
298
387
  },
299
388
  expire(locators) {
300
389
  expiring = [...locators];
301
390
  },
302
- notifyRoomVersion({ locator, roomVersion }) {
303
- for (const observer of observers)
304
- observer({ kind: 'room-version', locator, roomVersion });
391
+ notifyRoomVersion({ locator }) {
392
+ carry(locator);
305
393
  },
306
- notifyAuthorityChanged(scope) {
307
- for (const observer of observers) {
308
- observer(scope === undefined
309
- ? { kind: 'authority-changed' }
310
- : { kind: 'authority-changed', scope });
311
- }
394
+ notifyAuthorityChanged(locators) {
395
+ const named = new Set(locators ?? [...declaredRooms, ...rooms.map((room) => room.locator)]);
396
+ const listed = rooms.filter((room) => named.has(room.locator));
397
+ const left = [...named].filter((locator) => !listed.some((room) => room.locator === locator));
398
+ for (const room of listed)
399
+ declaredRooms.add(room.locator);
400
+ for (const locator of left)
401
+ declaredRooms.delete(locator);
402
+ emitAll({
403
+ kind: 'authority-changed',
404
+ rooms: listed.map(declared),
405
+ left,
406
+ ...(options.member === undefined ? {} : { member: options.member }),
407
+ });
312
408
  },
313
409
  notifyConversation(event) {
314
- if (event.kind === 'accepted-message') {
315
- sealed.add(event.message.turn);
316
- unsealed.delete(event.message.turn);
317
- }
318
410
  for (const listener of listeners)
319
411
  listener(event);
320
412
  },
@@ -323,21 +415,31 @@ export function createMemoryAdapter(options = {}) {
323
415
  observer(mode);
324
416
  },
325
417
  heldRooms() {
326
- return held;
418
+ return [...carried.keys()].sort();
327
419
  },
328
420
  replicatedConversations() {
329
421
  return replicated;
330
422
  },
423
+ heldConversationCursor() {
424
+ return heldCursor;
425
+ },
331
426
  conversations: {
332
427
  replicate(next) {
333
428
  replicated = [...next];
334
429
  },
430
+ hold(cursor) {
431
+ heldCursor = cursor;
432
+ },
335
433
  observe(notify) {
336
434
  listeners.add(notify);
337
435
  return () => {
338
436
  listeners.delete(notify);
339
437
  };
340
438
  },
439
+ history: (request) => Promise.resolve(historiesOf(request)),
440
+ ...(options.agents === undefined
441
+ ? {}
442
+ : { agents: () => Promise.resolve(structuredClone(options.agents)) }),
341
443
  },
342
444
  serviceMode: {
343
445
  observe(notify) {
@@ -349,44 +451,45 @@ export function createMemoryAdapter(options = {}) {
349
451
  },
350
452
  roomStream: {
351
453
  hold(asked) {
352
- held = asked.map((room) => room.locator);
353
- const expired = expiring.filter((locator) => held.includes(locator));
354
- expiring = [];
355
- const moved = asked.flatMap((room) => {
454
+ const named = asked.map((room) => room.locator);
455
+ const unknown = asked.flatMap((room) => {
356
456
  const current = rooms.find((entry) => entry.locator === room.locator);
357
457
  return current === undefined ||
358
- expired.includes(room.locator) ||
359
- current.roomVersion === room.roomVersion
458
+ current.roomVersion === room.roomVersion ||
459
+ served.get(room.locator)?.has(room.roomVersion) === true
360
460
  ? []
361
- : [{ locator: room.locator, roomVersion: current.roomVersion }];
461
+ : [room.locator];
362
462
  });
463
+ const expired = [
464
+ ...new Set([...expiring.filter((locator) => named.includes(locator)), ...unknown]),
465
+ ];
466
+ expiring = expiring.filter((locator) => !named.includes(locator));
363
467
  queueMicrotask(() => {
364
- for (const observer of observers) {
365
- for (const room of moved)
366
- observer({ kind: 'room-version', ...room });
367
- observer({ kind: 'opening', expired });
468
+ for (const room of asked) {
469
+ if (expired.includes(room.locator))
470
+ carried.delete(room.locator);
471
+ else {
472
+ carried.set(room.locator, room.roomVersion);
473
+ carry(room.locator);
474
+ }
368
475
  }
476
+ emitAll({ kind: 'caught-up', expired });
369
477
  });
370
478
  },
371
- reopen() {
372
- queueMicrotask(() => {
373
- for (const observer of observers) {
374
- observer({ kind: 'closed', revoked: false, lost: false });
375
- }
376
- });
377
- },
378
- published() { },
379
479
  observe(notify) {
380
480
  observers.add(notify);
381
481
  queueMicrotask(() => {
382
482
  if (!observers.has(notify))
383
483
  return;
384
484
  if (options.unopened === true) {
385
- notify({ kind: 'closed', revoked: false, lost: true });
485
+ notify({ kind: 'closed', revoked: false, lost: true, authorityChanged: false });
386
486
  return;
387
487
  }
388
488
  announceList();
389
- notify({ kind: 'opened' });
489
+ opened((event) => {
490
+ if (observers.has(notify))
491
+ notify(event);
492
+ });
390
493
  });
391
494
  return () => {
392
495
  observers.delete(notify);
@@ -403,12 +506,7 @@ export function createMemoryAdapter(options = {}) {
403
506
  return resolveScript(scripts.authorize, () => ({}), undefined);
404
507
  },
405
508
  async discover(_authorization) {
406
- return resolveScript(scripts.discover, discovery, undefined).then((answer) => {
407
- if (answer.kind === 'discovery')
408
- for (const room of answer.rooms)
409
- note(room.locator, room);
410
- return answer;
411
- });
509
+ return discovered();
412
510
  },
413
511
  reached,
414
512
  follows(locator, roomVersion, next) {
@@ -487,32 +585,42 @@ export function createMemoryAdapter(options = {}) {
487
585
  },
488
586
  async binary(_authorization, request) {
489
587
  return resolveScript(scripts.binary, () => {
490
- if (request.kind === 'reference') {
491
- return { kind: 'rejected', code: 'unscripted-binary', fields: {}, retryable: false };
492
- }
493
588
  const room = rooms.find((entry) => entry.locator === request.locator);
494
589
  if (room === undefined)
495
590
  return { kind: 'room-not-readable', locator: request.locator };
496
- const blob = room.blobs?.[request.path];
591
+ const blob = heldBlob(room, request.path);
497
592
  return blob === undefined
498
593
  ? { kind: 'rejected', code: 'file_not_found', fields: {}, retryable: false }
499
594
  : { kind: 'binary', body: blob.stream(), mediaType: blob.type };
500
595
  }, request);
501
596
  },
502
597
  async link(_authorization, request) {
503
- return resolveScript(scripts.link, () => ({ kind: 'rejected', code: 'unscripted-link', fields: {}, retryable: false }), request);
598
+ return resolveScript(scripts.link, () => {
599
+ const room = rooms.find((entry) => entry.locator === request.locator);
600
+ if (room === undefined)
601
+ return { kind: 'room-not-readable', locator: request.locator };
602
+ const blob = heldBlob(room, request.path);
603
+ if (blob === undefined) {
604
+ return { kind: 'rejected', code: 'file_not_found', fields: {}, retryable: false };
605
+ }
606
+ const url = linked.get(blob) ?? URL.createObjectURL(blob);
607
+ linked.set(blob, url);
608
+ return { kind: 'link', url, name: lastName(request.path), mediaType: blob.type };
609
+ }, request);
504
610
  },
505
- referrerScope(referrer) {
506
- if (typeof referrer !== 'object' || referrer === null)
507
- return null;
508
- const { room, path } = referrer;
509
- if (typeof room !== 'string')
510
- return null;
511
- return { locator: room, ...(typeof path === 'string' ? { path } : {}) };
611
+ async attach(_authorization, request) {
612
+ return resolveScript(scripts.attach, () => {
613
+ const room = rooms.find((entry) => entry.locator === request.locator);
614
+ if (room === undefined)
615
+ return { kind: 'room-not-readable', locator: request.locator };
616
+ const path = freePath(room, request.name);
617
+ attached.set(`${room.locator}\u0000${path}`, request.blob);
618
+ return { kind: 'attached', path, revision: `attached-${attached.size}` };
619
+ }, request);
512
620
  },
513
621
  async action(_authorization, request) {
514
622
  return resolveScript(scripts.action, () => {
515
- if (request.name.startsWith('conversation.'))
623
+ if (request.name.startsWith('conversations/'))
516
624
  return conversationAction(request);
517
625
  return { kind: 'rejected', code: 'unscripted-action', fields: {}, retryable: false };
518
626
  }, 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":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,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 { sql } from '@agentshouse/mdmodel';
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 ${sql.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
+ }