@agentshouse/mdruntime 0.1.0-ca9afacd5aebd → 0.1.0-cac707d698ca2

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