@agentshouse/mdruntime 0.1.0-ca6edc1abd618 → 0.1.0-ca92b54804c33

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 (149) hide show
  1. package/README.md +2 -2
  2. package/dist/adapter.d.ts +101 -70
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/attached-model.d.ts +3 -10
  5. package/dist/attached-model.d.ts.map +1 -1
  6. package/dist/attached-model.js +29 -47
  7. package/dist/bootstrap.d.ts +16 -14
  8. package/dist/bootstrap.d.ts.map +1 -1
  9. package/dist/bootstrap.js +272 -129
  10. package/dist/chain.d.ts +2 -1
  11. package/dist/chain.d.ts.map +1 -1
  12. package/dist/change.d.ts +1 -0
  13. package/dist/change.d.ts.map +1 -1
  14. package/dist/client.d.ts +13 -9
  15. package/dist/client.d.ts.map +1 -1
  16. package/dist/client.js +54 -6
  17. package/dist/collection.d.ts +7 -4
  18. package/dist/collection.d.ts.map +1 -1
  19. package/dist/collection.js +23 -17
  20. package/dist/compile.d.ts +1 -6
  21. package/dist/compile.d.ts.map +1 -1
  22. package/dist/compile.js +108 -243
  23. package/dist/connection.d.ts +14 -11
  24. package/dist/connection.d.ts.map +1 -1
  25. package/dist/connection.js +2264 -1933
  26. package/dist/context.d.ts +2 -0
  27. package/dist/context.d.ts.map +1 -0
  28. package/dist/context.js +7 -0
  29. package/dist/conversation.d.ts +21 -17
  30. package/dist/conversation.d.ts.map +1 -1
  31. package/dist/conversation.js +405 -253
  32. package/dist/durability.d.ts +6 -1
  33. package/dist/durability.d.ts.map +1 -1
  34. package/dist/entry.d.ts +3 -2
  35. package/dist/entry.d.ts.map +1 -1
  36. package/dist/entry.js +18 -8
  37. package/dist/file.d.ts +2 -11
  38. package/dist/file.d.ts.map +1 -1
  39. package/dist/file.js +6 -4
  40. package/dist/host.d.ts +3 -7
  41. package/dist/host.d.ts.map +1 -1
  42. package/dist/host.js +26 -29
  43. package/dist/identity.d.ts +0 -1
  44. package/dist/identity.d.ts.map +1 -1
  45. package/dist/identity.js +0 -3
  46. package/dist/index.d.ts +12 -9
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +4 -1
  49. package/dist/job.d.ts +14 -0
  50. package/dist/job.d.ts.map +1 -0
  51. package/dist/job.js +28 -0
  52. package/dist/live.d.ts +5 -4
  53. package/dist/live.d.ts.map +1 -1
  54. package/dist/live.js +62 -20
  55. package/dist/media.d.ts +8 -0
  56. package/dist/media.d.ts.map +1 -0
  57. package/dist/media.js +218 -0
  58. package/dist/memory.d.ts +12 -5
  59. package/dist/memory.d.ts.map +1 -1
  60. package/dist/memory.js +216 -85
  61. package/dist/messages.d.ts +3 -3
  62. package/dist/messages.d.ts.map +1 -1
  63. package/dist/messages.js +20 -19
  64. package/dist/model.d.ts +8 -10
  65. package/dist/model.d.ts.map +1 -1
  66. package/dist/model.js +5 -17
  67. package/dist/observation.d.ts +3 -2
  68. package/dist/observation.d.ts.map +1 -1
  69. package/dist/observation.js +8 -3
  70. package/dist/outcome.d.ts +13 -5
  71. package/dist/outcome.d.ts.map +1 -1
  72. package/dist/outcome.js +16 -2
  73. package/dist/overlay.d.ts +1 -0
  74. package/dist/overlay.d.ts.map +1 -1
  75. package/dist/overlay.js +11 -0
  76. package/dist/paths.d.ts +1 -0
  77. package/dist/paths.d.ts.map +1 -1
  78. package/dist/paths.js +4 -0
  79. package/dist/query.d.ts +8 -207
  80. package/dist/query.d.ts.map +1 -1
  81. package/dist/query.js +3 -505
  82. package/dist/reach.d.ts +18 -11
  83. package/dist/reach.d.ts.map +1 -1
  84. package/dist/reach.js +195 -126
  85. package/dist/react.d.ts +3 -1
  86. package/dist/react.d.ts.map +1 -1
  87. package/dist/readiness.d.ts +3 -17
  88. package/dist/readiness.d.ts.map +1 -1
  89. package/dist/readiness.js +4 -10
  90. package/dist/recovery.d.ts +1 -0
  91. package/dist/recovery.d.ts.map +1 -1
  92. package/dist/recovery.js +35 -1
  93. package/dist/refresh.d.ts +7 -5
  94. package/dist/refresh.d.ts.map +1 -1
  95. package/dist/refresh.js +33 -5
  96. package/dist/replica.d.ts +10 -8
  97. package/dist/replica.d.ts.map +1 -1
  98. package/dist/replica.js +52 -20
  99. package/dist/room-draft.d.ts +21 -5
  100. package/dist/room-draft.d.ts.map +1 -1
  101. package/dist/room-draft.js +98 -51
  102. package/dist/rows.d.ts +18 -16
  103. package/dist/rows.d.ts.map +1 -1
  104. package/dist/rows.js +45 -153
  105. package/dist/save.d.ts +2 -0
  106. package/dist/save.d.ts.map +1 -1
  107. package/dist/save.js +22 -13
  108. package/dist/search.d.ts +12 -63
  109. package/dist/search.d.ts.map +1 -1
  110. package/dist/search.js +9 -52
  111. package/dist/shared.d.ts +0 -1
  112. package/dist/shared.d.ts.map +1 -1
  113. package/dist/shared.js +0 -10
  114. package/dist/status.d.ts +4 -5
  115. package/dist/status.d.ts.map +1 -1
  116. package/dist/status.js +3 -7
  117. package/dist/store.d.ts +101 -61
  118. package/dist/store.d.ts.map +1 -1
  119. package/dist/store.js +153 -161
  120. package/dist/stored.d.ts +66 -55
  121. package/dist/stored.d.ts.map +1 -1
  122. package/dist/stored.js +341 -273
  123. package/dist/tables.d.ts +90 -63
  124. package/dist/tables.d.ts.map +1 -1
  125. package/dist/tables.js +174 -98
  126. package/dist/transfer.d.ts +0 -5
  127. package/dist/transfer.d.ts.map +1 -1
  128. package/dist/validation.d.ts +3 -4
  129. package/dist/validation.d.ts.map +1 -1
  130. package/dist/validation.js +42 -100
  131. package/dist/windows.d.ts +1 -12
  132. package/dist/windows.d.ts.map +1 -1
  133. package/dist/windows.js +2 -75
  134. package/dist/wire.d.ts +17 -8
  135. package/dist/wire.d.ts.map +1 -1
  136. package/dist/wire.js +5 -2
  137. package/package.json +2 -2
  138. package/dist/fence.d.ts +0 -6
  139. package/dist/fence.d.ts.map +0 -1
  140. package/dist/fence.js +0 -10
  141. package/dist/fulltext.d.ts +0 -27
  142. package/dist/fulltext.d.ts.map +0 -1
  143. package/dist/fulltext.js +0 -368
  144. package/dist/reads.d.ts +0 -11
  145. package/dist/reads.d.ts.map +0 -1
  146. package/dist/reads.js +0 -100
  147. package/dist/words.d.ts +0 -19
  148. package/dist/words.d.ts.map +0 -1
  149. package/dist/words.js +0 -150
package/dist/memory.js CHANGED
@@ -1,7 +1,12 @@
1
+ import { documentFacts, parse } 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) {
@@ -48,13 +53,16 @@ export function createMemoryAdapter(options = {}) {
48
53
  const listeners = new Set();
49
54
  const modeObservers = new Set();
50
55
  const scripted = new Map();
51
- const unsealed = new Map();
52
- const sealed = new Set();
53
56
  const sentTurns = new Map();
54
57
  const historyPageSize = options.historyPageSize;
55
- let held = [];
58
+ const attached = new Map();
59
+ const linked = new WeakMap();
60
+ const carried = new Map();
56
61
  let expiring = [];
62
+ let openings = 0;
63
+ const declaredRooms = new Set();
57
64
  let replicated = [];
65
+ let heldCursor = null;
58
66
  let issued = 0;
59
67
  function scriptConversations(entries) {
60
68
  scripted.clear();
@@ -82,8 +90,48 @@ export function createMemoryAdapter(options = {}) {
82
90
  const wanted = ordinals.get(`${locator}\u0000${target}`);
83
91
  return held !== undefined && wanted !== undefined && held >= wanted;
84
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, parse(content, path), { 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
+ }
85
131
  function retain(entries) {
86
132
  for (const room of entries) {
133
+ for (const [path, content] of Object.entries(room.documents ?? {}))
134
+ factsAt(room, path, content);
87
135
  note(room.locator, room);
88
136
  const versions = served.get(room.locator) ?? new Map();
89
137
  versions.set(room.roomVersion, room);
@@ -116,10 +164,14 @@ export function createMemoryAdapter(options = {}) {
116
164
  roomVersion: room.roomVersion,
117
165
  ...(opening ? { models: modelsOf(room) } : {}),
118
166
  documents: named(room.documents ?? {}, taken),
167
+ facts: factsIn(room, taken),
119
168
  revisions: named(room.revisions ?? {}, [...taken, ...targets]),
120
169
  ...(room.fileIds === undefined
121
170
  ? {}
122
171
  : { fileIds: named(room.fileIds, [...taken, ...targets]) }),
172
+ ...(room.created === undefined
173
+ ? {}
174
+ : { created: named(room.created, [...taken, ...targets]) }),
123
175
  changes: named(room.changes ?? {}, [...taken, ...targets]),
124
176
  binaryFiles: named(room.binaryFiles ?? {}, targets),
125
177
  ...(opening && room.folders !== undefined ? { folders: room.folders } : {}),
@@ -136,18 +188,19 @@ export function createMemoryAdapter(options = {}) {
136
188
  ...(folderRemovals.length === 0 ? {} : { folderRemovals }),
137
189
  };
138
190
  }
139
- function advancement(room, from, at) {
191
+ function advancement(room, from, at, whole = false) {
140
192
  const current = room.documents ?? {};
141
193
  const previous = from.documents ?? {};
142
194
  const currentTargets = new Set(Object.keys(room.binaryFiles ?? {}));
143
195
  const previousTargets = new Set(Object.keys(from.binaryFiles ?? {}));
144
196
  const headMoved = (path) => room.revisions?.[path] !== from.revisions?.[path] ||
145
197
  room.fileIds?.[path] !== from.fileIds?.[path] ||
198
+ room.created?.[path] !== from.created?.[path] ||
146
199
  !sameChange(room.changes?.[path], from.changes?.[path]);
147
200
  const changed = Object.keys(current)
148
201
  .filter((path) => current[path] !== previous[path] || headMoved(path))
149
202
  .sort();
150
- const size = pageSize ?? changed.length;
203
+ const size = whole ? changed.length : (pageSize ?? changed.length);
151
204
  const taken = changed.slice(at, at + size);
152
205
  const next = at + taken.length;
153
206
  const opening = at === 0;
@@ -163,10 +216,14 @@ export function createMemoryAdapter(options = {}) {
163
216
  roomVersion: room.roomVersion,
164
217
  ...(models === undefined ? {} : { models }),
165
218
  documents: named(current, taken),
219
+ facts: factsIn(room, taken),
166
220
  revisions: named(room.revisions ?? {}, [...taken, ...targets]),
167
221
  ...(room.fileIds === undefined
168
222
  ? {}
169
223
  : { fileIds: named(room.fileIds, [...taken, ...targets]) }),
224
+ ...(room.created === undefined
225
+ ? {}
226
+ : { created: named(room.created, [...taken, ...targets]) }),
170
227
  changes: named(room.changes ?? {}, [...taken, ...targets]),
171
228
  binaryFiles: named(room.binaryFiles ?? {}, targets),
172
229
  ...(opening ? foldersMoved(room, from) : {}),
@@ -179,6 +236,26 @@ export function createMemoryAdapter(options = {}) {
179
236
  ...(pageToken === undefined ? {} : { pageToken }),
180
237
  };
181
238
  }
239
+ function carry(locator) {
240
+ const at = carried.get(locator);
241
+ const current = rooms.find((entry) => entry.locator === locator);
242
+ if (at === undefined || current === undefined || current.roomVersion === at)
243
+ return;
244
+ const from = served.get(locator)?.get(at);
245
+ carried.set(locator, current.roomVersion);
246
+ if (from === undefined)
247
+ return;
248
+ const page = advancement(current, from, 0, true);
249
+ for (const observer of observers) {
250
+ observer({
251
+ kind: 'room-change',
252
+ locator,
253
+ from: at,
254
+ roomVersion: current.roomVersion,
255
+ read: async () => page,
256
+ });
257
+ }
258
+ }
182
259
  function pageOf(conversation, after) {
183
260
  const messages = conversation.messages ?? [];
184
261
  const first = typeof after === 'string' ? messages.findIndex((message) => message.position > after) : 0;
@@ -193,16 +270,13 @@ export function createMemoryAdapter(options = {}) {
193
270
  : {}),
194
271
  };
195
272
  }
196
- function historiesOf(input) {
197
- const asked = input?.conversations;
198
- if (!Array.isArray(asked))
199
- return { kind: 'rejected', code: 'invalid_request', fields: {}, retryable: false };
273
+ function historiesOf(request) {
200
274
  const histories = [];
201
- for (const entry of asked) {
202
- const conversation = typeof entry.conversation === 'string' ? scripted.get(entry.conversation) : undefined;
275
+ for (const entry of request.conversations) {
276
+ const conversation = scripted.get(entry.conversation);
203
277
  if (conversation === undefined)
204
278
  continue;
205
- const outcome = conversation.outcomes?.['conversation.history'];
279
+ const outcome = conversation.outcomes?.history;
206
280
  if (outcome !== undefined)
207
281
  return outcome;
208
282
  histories.push(pageOf(conversation, entry.after));
@@ -213,15 +287,13 @@ export function createMemoryAdapter(options = {}) {
213
287
  function conversationAction(request) {
214
288
  const name = request.name;
215
289
  const input = (request.input ?? {});
216
- if (name === 'conversation.history')
217
- return historiesOf(request.input);
218
- if (name === 'conversation.create') {
290
+ if (name === 'conversations/create') {
219
291
  issued += 1;
220
292
  const conversation = `conversation-${issued}`;
221
293
  scripted.set(conversation, { conversation });
222
- return { kind: 'action', result: { conversation } };
294
+ return { kind: 'action', result: { conversation_id: conversation } };
223
295
  }
224
- const named = typeof input.conversation === 'string' ? input.conversation : '';
296
+ const named = typeof input.conversation_id === 'string' ? input.conversation_id : '';
225
297
  const conversation = scripted.get(named);
226
298
  if (conversation === undefined) {
227
299
  return {
@@ -234,34 +306,28 @@ export function createMemoryAdapter(options = {}) {
234
306
  const outcome = conversation.outcomes?.[name];
235
307
  if (outcome !== undefined)
236
308
  return outcome;
237
- if (name === 'conversation.send') {
309
+ if (name === 'conversations/send') {
238
310
  const operation = typeof input.operation_id === 'string' ? input.operation_id : null;
239
311
  const answered = operation === null ? undefined : sentTurns.get(operation);
240
312
  if (answered !== undefined)
241
- return { kind: 'action', result: { turn: answered } };
313
+ return { kind: 'action', result: { message_id: answered } };
242
314
  issued += 1;
243
315
  const turn = `turn-${issued}`;
244
- unsealed.set(turn, named);
245
316
  if (operation !== null)
246
317
  sentTurns.set(operation, turn);
247
- return { kind: 'action', result: { turn } };
318
+ return { kind: 'action', result: { message_id: turn } };
248
319
  }
249
- if (name === 'conversation.cancel') {
250
- const turn = typeof input.turn === 'string' ? input.turn : '';
251
- if (sealed.has(turn) || !unsealed.has(turn)) {
252
- return { kind: 'rejected', code: 'turn-sealed', fields: { turn }, retryable: false };
253
- }
254
- unsealed.delete(turn);
255
- return { kind: 'action', result: { turn, cancelled: true } };
256
- }
257
- if (name === 'conversation.delete')
320
+ if (name === 'conversations/delete')
258
321
  scripted.delete(named);
259
- return { kind: 'action', result: { conversation: named } };
322
+ return { kind: 'action', result: { conversation_id: named } };
260
323
  }
261
324
  function announceList() {
262
- const entries = [...scripted.values()].flatMap(({ conversation, listed }) => listed === undefined ? [] : [{ conversation, ...listed }]);
263
- for (const listener of listeners)
264
- listener({ kind: 'list', entries });
325
+ const entries = [...scripted.values()].flatMap(({ conversation, listed, messages = [] }) => listed === undefined
326
+ ? []
327
+ : [{ conversation, ...listed, newest: messages.at(-1)?.position ?? null }]);
328
+ for (const listener of listeners) {
329
+ listener({ kind: 'list', entries, whole: true, more: false, cursor: null });
330
+ }
265
331
  }
266
332
  function discovery() {
267
333
  return {
@@ -273,9 +339,42 @@ export function createMemoryAdapter(options = {}) {
273
339
  ...(options.member === undefined ? {} : { member: options.member }),
274
340
  };
275
341
  }
342
+ function discovered() {
343
+ return resolveScript(scripts.discover, discovery, undefined).then((answer) => {
344
+ if (answer.kind === 'discovery')
345
+ for (const room of answer.rooms)
346
+ note(room.locator, room);
347
+ return answer;
348
+ });
349
+ }
350
+ function opened(notify) {
351
+ const opening = ++openings;
352
+ notify({ kind: 'opened' });
353
+ void discovered()
354
+ .catch(() => ({ kind: 'transport-uncertainty' }))
355
+ .then((answer) => {
356
+ if (opening !== openings)
357
+ return;
358
+ if (answer.kind !== 'discovery') {
359
+ openings += 1;
360
+ carried.clear();
361
+ const revoked = answer.kind === 'revoked-authorization';
362
+ notify({ kind: 'closed', revoked, lost: !revoked, authorityChanged: false });
363
+ return;
364
+ }
365
+ declaredRooms.clear();
366
+ for (const room of answer.rooms)
367
+ declaredRooms.add(room.locator);
368
+ notify({ kind: 'discovered', discovery: answer });
369
+ });
370
+ }
371
+ function emitAll(event) {
372
+ for (const observer of observers)
373
+ observer(event);
374
+ }
276
375
  return {
277
- ...(options.exhaustiveDiscovery === undefined ? {} : { exhaustiveDiscovery: true }),
278
376
  ...(options.roomTypes === undefined ? {} : { roomTypes: options.roomTypes }),
377
+ ...(options.typedPaths === undefined ? {} : { typedPaths: options.typedPaths }),
279
378
  setRooms(next) {
280
379
  rooms = [...next];
281
380
  retain(rooms);
@@ -284,33 +383,37 @@ export function createMemoryAdapter(options = {}) {
284
383
  scriptConversations(next);
285
384
  },
286
385
  open() {
386
+ carried.clear();
287
387
  announceList();
288
- for (const observer of observers)
289
- observer({ kind: 'opened' });
388
+ opened(emitAll);
290
389
  },
291
- close({ revoked = false, lost = !revoked } = {}) {
292
- for (const observer of observers)
293
- observer({ kind: 'closed', revoked, lost });
390
+ close({ revoked = false, lost = !revoked, authorityChanged = false } = {}) {
391
+ openings += 1;
392
+ carried.clear();
393
+ emitAll({ kind: 'closed', revoked, lost, authorityChanged });
294
394
  },
295
395
  expire(locators) {
296
396
  expiring = [...locators];
297
397
  },
298
- notifyRoomVersion({ locator, roomVersion }) {
299
- for (const observer of observers)
300
- observer({ kind: 'room-version', locator, roomVersion });
398
+ notifyRoomVersion({ locator }) {
399
+ carry(locator);
301
400
  },
302
- notifyAuthorityChanged(scope) {
303
- for (const observer of observers) {
304
- observer(scope === undefined
305
- ? { kind: 'authority-changed' }
306
- : { kind: 'authority-changed', scope });
307
- }
401
+ notifyAuthorityChanged(locators) {
402
+ const named = new Set(locators ?? [...declaredRooms, ...rooms.map((room) => room.locator)]);
403
+ const listed = rooms.filter((room) => named.has(room.locator));
404
+ const left = [...named].filter((locator) => !listed.some((room) => room.locator === locator));
405
+ for (const room of listed)
406
+ declaredRooms.add(room.locator);
407
+ for (const locator of left)
408
+ declaredRooms.delete(locator);
409
+ emitAll({
410
+ kind: 'authority-changed',
411
+ rooms: listed.map(declared),
412
+ left,
413
+ ...(options.member === undefined ? {} : { member: options.member }),
414
+ });
308
415
  },
309
416
  notifyConversation(event) {
310
- if (event.kind === 'accepted-message') {
311
- sealed.add(event.message.turn);
312
- unsealed.delete(event.message.turn);
313
- }
314
417
  for (const listener of listeners)
315
418
  listener(event);
316
419
  },
@@ -319,21 +422,31 @@ export function createMemoryAdapter(options = {}) {
319
422
  observer(mode);
320
423
  },
321
424
  heldRooms() {
322
- return held;
425
+ return [...carried.keys()].sort();
323
426
  },
324
427
  replicatedConversations() {
325
428
  return replicated;
326
429
  },
430
+ heldConversationCursor() {
431
+ return heldCursor;
432
+ },
327
433
  conversations: {
328
434
  replicate(next) {
329
435
  replicated = [...next];
330
436
  },
437
+ hold(cursor) {
438
+ heldCursor = cursor;
439
+ },
331
440
  observe(notify) {
332
441
  listeners.add(notify);
333
442
  return () => {
334
443
  listeners.delete(notify);
335
444
  };
336
445
  },
446
+ history: (request) => Promise.resolve(historiesOf(request)),
447
+ ...(options.agents === undefined
448
+ ? {}
449
+ : { agents: () => Promise.resolve(structuredClone(options.agents)) }),
337
450
  },
338
451
  serviceMode: {
339
452
  observe(notify) {
@@ -345,44 +458,46 @@ export function createMemoryAdapter(options = {}) {
345
458
  },
346
459
  roomStream: {
347
460
  hold(asked) {
348
- held = asked.map((room) => room.locator);
349
- const expired = expiring.filter((locator) => held.includes(locator));
350
- expiring = [];
351
- const moved = asked.flatMap((room) => {
461
+ const named = asked.map((room) => room.locator);
462
+ const unknown = asked.flatMap((room) => {
352
463
  const current = rooms.find((entry) => entry.locator === room.locator);
464
+ const version = room.roomVersion;
353
465
  return current === undefined ||
354
- expired.includes(room.locator) ||
355
- current.roomVersion === room.roomVersion
466
+ (version !== null &&
467
+ (current.roomVersion === version || served.get(room.locator)?.has(version) === true))
356
468
  ? []
357
- : [{ locator: room.locator, roomVersion: current.roomVersion }];
469
+ : [room.locator];
358
470
  });
471
+ const expired = [
472
+ ...new Set([...expiring.filter((locator) => named.includes(locator)), ...unknown]),
473
+ ];
474
+ expiring = expiring.filter((locator) => !named.includes(locator));
359
475
  queueMicrotask(() => {
360
- for (const observer of observers) {
361
- for (const room of moved)
362
- observer({ kind: 'room-version', ...room });
363
- observer({ kind: 'opening', expired });
476
+ for (const room of asked) {
477
+ if (expired.includes(room.locator) || room.roomVersion === null)
478
+ carried.delete(room.locator);
479
+ else {
480
+ carried.set(room.locator, room.roomVersion);
481
+ carry(room.locator);
482
+ }
364
483
  }
484
+ emitAll({ kind: 'caught-up', expired });
365
485
  });
366
486
  },
367
- reopen() {
368
- queueMicrotask(() => {
369
- for (const observer of observers) {
370
- observer({ kind: 'closed', revoked: false, lost: false });
371
- }
372
- });
373
- },
374
- published() { },
375
487
  observe(notify) {
376
488
  observers.add(notify);
377
489
  queueMicrotask(() => {
378
490
  if (!observers.has(notify))
379
491
  return;
380
492
  if (options.unopened === true) {
381
- notify({ kind: 'closed', revoked: false, lost: true });
493
+ notify({ kind: 'closed', revoked: false, lost: true, authorityChanged: false });
382
494
  return;
383
495
  }
384
496
  announceList();
385
- notify({ kind: 'opened' });
497
+ opened((event) => {
498
+ if (observers.has(notify))
499
+ notify(event);
500
+ });
386
501
  });
387
502
  return () => {
388
503
  observers.delete(notify);
@@ -399,12 +514,7 @@ export function createMemoryAdapter(options = {}) {
399
514
  return resolveScript(scripts.authorize, () => ({}), undefined);
400
515
  },
401
516
  async discover(_authorization) {
402
- return resolveScript(scripts.discover, discovery, undefined).then((answer) => {
403
- if (answer.kind === 'discovery')
404
- for (const room of answer.rooms)
405
- note(room.locator, room);
406
- return answer;
407
- });
517
+ return discovered();
408
518
  },
409
519
  reached,
410
520
  follows(locator, roomVersion, next) {
@@ -486,18 +596,39 @@ export function createMemoryAdapter(options = {}) {
486
596
  const room = rooms.find((entry) => entry.locator === request.locator);
487
597
  if (room === undefined)
488
598
  return { kind: 'room-not-readable', locator: request.locator };
489
- const blob = room.blobs?.[request.path];
599
+ const blob = heldBlob(room, request.path);
490
600
  return blob === undefined
491
601
  ? { kind: 'rejected', code: 'file_not_found', fields: {}, retryable: false }
492
602
  : { kind: 'binary', body: blob.stream(), mediaType: blob.type };
493
603
  }, request);
494
604
  },
495
605
  async link(_authorization, request) {
496
- return resolveScript(scripts.link, () => ({ kind: 'rejected', code: 'unscripted-link', fields: {}, retryable: false }), request);
606
+ return resolveScript(scripts.link, () => {
607
+ const room = rooms.find((entry) => entry.locator === request.locator);
608
+ if (room === undefined)
609
+ return { kind: 'room-not-readable', locator: request.locator };
610
+ const blob = heldBlob(room, request.path);
611
+ if (blob === undefined) {
612
+ return { kind: 'rejected', code: 'file_not_found', fields: {}, retryable: false };
613
+ }
614
+ const url = linked.get(blob) ?? URL.createObjectURL(blob);
615
+ linked.set(blob, url);
616
+ return { kind: 'link', url, name: lastName(request.path), mediaType: blob.type };
617
+ }, request);
618
+ },
619
+ async attach(_authorization, request) {
620
+ return resolveScript(scripts.attach, () => {
621
+ const room = rooms.find((entry) => entry.locator === request.locator);
622
+ if (room === undefined)
623
+ return { kind: 'room-not-readable', locator: request.locator };
624
+ const path = freePath(room, request.name);
625
+ attached.set(`${room.locator}\u0000${path}`, request.blob);
626
+ return { kind: 'attached', path, revision: `attached-${attached.size}` };
627
+ }, request);
497
628
  },
498
629
  async action(_authorization, request) {
499
630
  return resolveScript(scripts.action, () => {
500
- if (request.name.startsWith('conversation.'))
631
+ if (request.name.startsWith('conversations/'))
501
632
  return conversationAction(request);
502
633
  return { kind: 'rejected', code: 'unscripted-action', fields: {}, retryable: false };
503
634
  }, request).then((answer) => {
@@ -18,10 +18,10 @@ export type ConversationWrite = {
18
18
  readonly partial: boolean;
19
19
  };
20
20
  };
21
- export declare function heldConversations(connection: Connection): HeldConversation[];
21
+ export declare function heldConversations(connection: Connection, conversations: readonly string[] | null): HeldConversation[];
22
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): MessageWindow;
24
- export declare function messageCount(connection: Connection, conversation: string): number;
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
25
  export declare function turnArrived(connection: Connection, conversation: string, turn: string): boolean;
26
26
  export declare function droppedMessages(connection: Connection, conversations: readonly string[]): void;
27
27
  export declare function forgottenMessages(connection: Connection): void;
@@ -1 +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,CAoCN;AAYD,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,GACvC,aAAa,CAUf;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAMjF;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,CAW9F;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAG9D"}
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,CAC/B,UAAU,EAAE,UAAU,EACtB,aAAa,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,GACtC,gBAAgB,EAAE,CA0BpB;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"}
package/dist/messages.js CHANGED
@@ -1,4 +1,4 @@
1
- import { indexMessage, unindexMessages } from './fulltext.js';
1
+ import { sql } from '@agentshouse/mdmodel';
2
2
  export const MESSAGE_TABLES = [
3
3
  'CREATE TABLE conversation_reads (conversation TEXT PRIMARY KEY, read TEXT, partial INTEGER NOT NULL) WITHOUT ROWID',
4
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)',
@@ -9,16 +9,18 @@ export const MESSAGE_TABLES = [
9
9
  function text(value) {
10
10
  return typeof value === 'string' ? value : String(value);
11
11
  }
12
- export function heldConversations(connection) {
12
+ export function heldConversations(connection, conversations) {
13
+ const named = conversations === null ? '' : ' AND conversation IN (SELECT value FROM json_each(?))';
14
+ const bound = conversations === null ? [] : [JSON.stringify(conversations)];
13
15
  const held = new Map();
14
- for (const row of connection.rows('SELECT conversation, read, partial FROM conversation_reads')) {
16
+ for (const row of connection.rows(`SELECT conversation, read, partial FROM conversation_reads WHERE 1 = 1${named}`, bound)) {
15
17
  held.set(text(row.conversation), {
16
18
  conversation: text(row.conversation),
17
19
  read: row.read === null ? null : text(row.read),
18
20
  partial: Number(row.partial) === 1,
19
21
  });
20
22
  }
21
- for (const row of connection.rows('SELECT DISTINCT conversation FROM conversation_messages WHERE conversation NOT IN (SELECT conversation FROM conversation_reads)')) {
23
+ for (const row of connection.rows(`SELECT DISTINCT conversation FROM conversation_messages WHERE conversation NOT IN (SELECT conversation FROM conversation_reads)${named}`, bound)) {
22
24
  held.set(text(row.conversation), {
23
25
  conversation: text(row.conversation),
24
26
  read: undefined,
@@ -36,17 +38,12 @@ export function keptMessages(connection, { conversation, messages, read }) {
36
38
  JSON.stringify(message.message ?? null),
37
39
  message.text,
38
40
  ];
39
- let id;
40
41
  if (held === undefined) {
41
42
  connection.run('INSERT INTO conversation_messages (conversation, identity, turn, position, message, text) VALUES (?, ?, ?, ?, ?, ?)', [conversation, message.identity, ...record]);
42
- id = Number(connection.rows('SELECT last_insert_rowid() AS id')[0].id);
43
43
  }
44
44
  else {
45
- id = Number(held.id);
46
- unindexMessages(connection, [id]);
47
- connection.run('UPDATE conversation_messages SET turn = ?, position = ?, message = ?, text = ? WHERE id = ?', [...record, id]);
45
+ connection.run('UPDATE conversation_messages SET turn = ?, position = ?, message = ?, text = ? WHERE id = ?', [...record, held.id]);
48
46
  }
49
- indexMessage(connection, id, message.text);
50
47
  }
51
48
  if (read !== undefined) {
52
49
  connection.run('INSERT OR REPLACE INTO conversation_reads (conversation, read, partial) VALUES (?, ?, ?)', [conversation, read.at, read.partial ? 1 : 0]);
@@ -62,14 +59,21 @@ function accepted(row) {
62
59
  message: JSON.parse(text(row.message)),
63
60
  };
64
61
  }
65
- export function messageWindow(connection, conversation, limit, before) {
66
- const rows = connection.rows(`SELECT identity, turn, position, message, text FROM conversation_messages WHERE conversation = ?${before === null ? '' : ' AND (position < ? OR (position = ? AND identity < ?))'} ORDER BY position DESC, identity DESC LIMIT ?`, before === null
67
- ? [conversation, limit + 1]
68
- : [conversation, before[0], before[0], before[1], limit + 1]);
62
+ function holding(phrase) {
63
+ return phrase === null
64
+ ? { sql: '', bind: [] }
65
+ : { sql: ` AND ${sql.HOLDS_PHRASE}(text, ?)`, bind: [phrase.toLowerCase()] };
66
+ }
67
+ export function messageWindow(connection, conversation, limit, before, phrase) {
68
+ const held = holding(phrase);
69
+ 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
70
+ ? [conversation, ...held.bind, limit + 1]
71
+ : [conversation, ...held.bind, before[0], before[0], before[1], limit + 1]);
69
72
  return { messages: rows.slice(0, limit).map(accepted).reverse(), more: rows.length > limit };
70
73
  }
71
- export function messageCount(connection, conversation) {
72
- const [counted] = connection.rows('SELECT count(*) AS held FROM conversation_messages WHERE conversation = ?', [conversation]);
74
+ export function messageCount(connection, conversation, phrase) {
75
+ const held = holding(phrase);
76
+ const [counted] = connection.rows(`SELECT count(*) AS held FROM conversation_messages WHERE conversation = ?${held.sql}`, [conversation, ...held.bind]);
73
77
  return Number(counted?.held ?? 0);
74
78
  }
75
79
  export function turnArrived(connection, conversation, turn) {
@@ -77,9 +81,6 @@ export function turnArrived(connection, conversation, turn) {
77
81
  }
78
82
  export function droppedMessages(connection, conversations) {
79
83
  for (const conversation of conversations) {
80
- unindexMessages(connection, connection
81
- .rows('SELECT id FROM conversation_messages WHERE conversation = ?', [conversation])
82
- .map((row) => Number(row.id)));
83
84
  connection.run('DELETE FROM conversation_messages WHERE conversation = ?', [conversation]);
84
85
  connection.run('DELETE FROM conversation_reads WHERE conversation = ?', [conversation]);
85
86
  }
package/dist/model.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Definition, ValueFacts, VerifiedModel } from '@agentshouse/mdmodel';
1
+ import type { Definition, VerifiedModel } from '@agentshouse/mdmodel';
2
2
  import type { Open } from './open.js';
3
3
  export type ModelIdentity = {
4
4
  identity: string;
@@ -8,7 +8,7 @@ export type ModelSnapshot = ModelIdentity & {
8
8
  schema: string | null;
9
9
  revision: string | null;
10
10
  };
11
- export type ModelUnavailableReason = Open<'not-readable' | 'revoked' | 'not-acquired' | 'invalid'>;
11
+ export type ModelUnavailableReason = Open<'not-readable' | 'revoked' | 'not-acquired'>;
12
12
  export type ModelResult = {
13
13
  kind: 'model';
14
14
  snapshot: ModelSnapshot;
@@ -20,20 +20,21 @@ export type MdType = Definition;
20
20
  export type ModelState = {
21
21
  status: 'unavailable';
22
22
  reason: ModelUnavailableReason;
23
- schema?: string | null;
24
- revision?: string | null;
25
23
  } | {
26
24
  status: 'current' | 'cached';
27
25
  snapshot: ModelSnapshot;
28
26
  verifiedWith: ModelIdentity;
29
27
  types: VerifiedModel['types'];
30
28
  };
29
+ export type StoredModel = Extract<ModelState, {
30
+ status: 'unavailable';
31
+ }> | Omit<Extract<ModelState, {
32
+ status: 'current' | 'cached';
33
+ }>, 'types'>;
31
34
  export type TypesResult = {
32
35
  status: Open<'unavailable'>;
33
36
  reason: ModelUnavailableReason;
34
37
  types: null;
35
- schema?: string | null;
36
- revision?: string | null;
37
38
  } | {
38
39
  status: Open<'current' | 'cached'>;
39
40
  identity: string;
@@ -51,8 +52,5 @@ export type ValidationReceipt = {
51
52
  export declare const NO_MODEL: ModelState;
52
53
  export declare function definitionOf(state: ModelState, type: string | undefined): Definition | undefined;
53
54
  export declare function modelTypes(state: ModelState): TypesResult;
54
- export declare function cached(state: ModelState): ModelState;
55
- export declare function sameModel(left: ModelIdentity, right: ModelIdentity): boolean;
56
- export declare function sameTyping(left: ModelState, right: ModelState): boolean;
57
- export declare function referenceField(facts: ValueFacts | undefined): boolean;
55
+ export declare function storedModel(state: ModelState): StoredModel;
58
56
  //# sourceMappingURL=model.d.ts.map