@agentshouse/mdruntime 0.1.0-ce531b51ff088 → 0.1.0-ce6bc9239a3ee

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 (253) hide show
  1. package/README.md +25 -12
  2. package/dist/acceptance.d.ts +26 -0
  3. package/dist/acceptance.d.ts.map +1 -0
  4. package/dist/acceptance.js +384 -0
  5. package/dist/adapter.d.ts +218 -62
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/adapter.js +1 -9
  8. package/dist/attached-model.d.ts +8 -0
  9. package/dist/attached-model.d.ts.map +1 -0
  10. package/dist/attached-model.js +46 -0
  11. package/dist/attempt.d.ts +16 -0
  12. package/dist/attempt.d.ts.map +1 -0
  13. package/dist/attempt.js +70 -0
  14. package/dist/binary.d.ts +18 -0
  15. package/dist/binary.d.ts.map +1 -0
  16. package/dist/binary.js +8 -0
  17. package/dist/bootstrap.d.ts +40 -6
  18. package/dist/bootstrap.d.ts.map +1 -1
  19. package/dist/bootstrap.js +336 -51
  20. package/dist/chain.d.ts +12 -0
  21. package/dist/chain.d.ts.map +1 -0
  22. package/dist/chain.js +64 -0
  23. package/dist/change-set.d.ts +9 -14
  24. package/dist/change-set.d.ts.map +1 -1
  25. package/dist/change-set.js +11 -56
  26. package/dist/change.d.ts +46 -0
  27. package/dist/change.d.ts.map +1 -1
  28. package/dist/change.js +34 -0
  29. package/dist/chat-types.d.ts +3 -0
  30. package/dist/chat-types.d.ts.map +1 -0
  31. package/dist/chat-types.js +2 -0
  32. package/dist/client.d.ts +50 -18
  33. package/dist/client.d.ts.map +1 -1
  34. package/dist/client.js +162 -30
  35. package/dist/collection.d.ts +13 -14
  36. package/dist/collection.d.ts.map +1 -1
  37. package/dist/collection.js +52 -56
  38. package/dist/compile.d.ts +16 -0
  39. package/dist/compile.d.ts.map +1 -0
  40. package/dist/compile.js +717 -0
  41. package/dist/connection.d.ts +63 -48
  42. package/dist/connection.d.ts.map +1 -1
  43. package/dist/connection.js +2487 -1575
  44. package/dist/context.d.ts +2 -0
  45. package/dist/context.d.ts.map +1 -0
  46. package/dist/context.js +7 -0
  47. package/dist/conversation-row.d.ts +33 -0
  48. package/dist/conversation-row.d.ts.map +1 -0
  49. package/dist/conversation-row.js +47 -0
  50. package/dist/conversation.d.ts +72 -22
  51. package/dist/conversation.d.ts.map +1 -1
  52. package/dist/conversation.js +1008 -273
  53. package/dist/digest.d.ts +7 -0
  54. package/dist/digest.d.ts.map +1 -0
  55. package/dist/digest.js +113 -0
  56. package/dist/durability.d.ts +10 -1
  57. package/dist/durability.d.ts.map +1 -1
  58. package/dist/durability.js +5 -0
  59. package/dist/entry.d.ts +20 -0
  60. package/dist/entry.d.ts.map +1 -0
  61. package/dist/entry.js +161 -0
  62. package/dist/expectation.d.ts +21 -0
  63. package/dist/expectation.d.ts.map +1 -0
  64. package/dist/expectation.js +70 -0
  65. package/dist/failure.d.ts +13 -0
  66. package/dist/failure.d.ts.map +1 -0
  67. package/dist/failure.js +19 -0
  68. package/dist/figures.d.ts +48 -0
  69. package/dist/figures.d.ts.map +1 -0
  70. package/dist/figures.js +197 -0
  71. package/dist/file.d.ts +2 -19
  72. package/dist/file.d.ts.map +1 -1
  73. package/dist/file.js +7 -122
  74. package/dist/host.d.ts +18 -2
  75. package/dist/host.d.ts.map +1 -1
  76. package/dist/host.js +164 -50
  77. package/dist/identity.d.ts +3 -0
  78. package/dist/identity.d.ts.map +1 -1
  79. package/dist/identity.js +13 -1
  80. package/dist/index.d.ts +31 -10
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/index.js +13 -0
  83. package/dist/install.d.ts +86 -0
  84. package/dist/install.d.ts.map +1 -0
  85. package/dist/install.js +537 -0
  86. package/dist/job.d.ts +33 -0
  87. package/dist/job.d.ts.map +1 -0
  88. package/dist/job.js +54 -0
  89. package/dist/live.d.ts +23 -0
  90. package/dist/live.d.ts.map +1 -0
  91. package/dist/live.js +165 -0
  92. package/dist/log.d.ts +1 -0
  93. package/dist/log.d.ts.map +1 -1
  94. package/dist/media.d.ts +8 -0
  95. package/dist/media.d.ts.map +1 -0
  96. package/dist/media.js +218 -0
  97. package/dist/memory.d.ts +30 -9
  98. package/dist/memory.d.ts.map +1 -1
  99. package/dist/memory.js +337 -86
  100. package/dist/messages.d.ts +52 -0
  101. package/dist/messages.d.ts.map +1 -0
  102. package/dist/messages.js +168 -0
  103. package/dist/model.d.ts +17 -16
  104. package/dist/model.d.ts.map +1 -1
  105. package/dist/model.js +9 -9
  106. package/dist/name.d.ts +4 -0
  107. package/dist/name.d.ts.map +1 -0
  108. package/dist/name.js +122 -0
  109. package/dist/observation.d.ts +22 -2
  110. package/dist/observation.d.ts.map +1 -1
  111. package/dist/observation.js +133 -17
  112. package/dist/open.d.ts +2 -0
  113. package/dist/open.d.ts.map +1 -0
  114. package/dist/open.js +1 -0
  115. package/dist/opened.d.ts +22 -0
  116. package/dist/opened.d.ts.map +1 -0
  117. package/dist/opened.js +107 -0
  118. package/dist/outcome.d.ts +73 -5
  119. package/dist/outcome.d.ts.map +1 -1
  120. package/dist/outcome.js +84 -1
  121. package/dist/overlay.d.ts +16 -5
  122. package/dist/overlay.d.ts.map +1 -1
  123. package/dist/overlay.js +147 -180
  124. package/dist/parts.d.ts +59 -0
  125. package/dist/parts.d.ts.map +1 -0
  126. package/dist/parts.js +118 -0
  127. package/dist/paths.d.ts +1 -1
  128. package/dist/paths.d.ts.map +1 -1
  129. package/dist/paths.js +4 -16
  130. package/dist/query.d.ts +56 -180
  131. package/dist/query.d.ts.map +1 -1
  132. package/dist/query.js +3 -422
  133. package/dist/questions.d.ts +32 -0
  134. package/dist/questions.d.ts.map +1 -0
  135. package/dist/questions.js +118 -0
  136. package/dist/reach.d.ts +54 -0
  137. package/dist/reach.d.ts.map +1 -0
  138. package/dist/reach.js +494 -0
  139. package/dist/react.d.ts +46 -0
  140. package/dist/react.d.ts.map +1 -0
  141. package/dist/react.js +54 -0
  142. package/dist/read.d.ts +7 -0
  143. package/dist/read.d.ts.map +1 -0
  144. package/dist/read.js +17 -0
  145. package/dist/readiness.d.ts +50 -0
  146. package/dist/readiness.d.ts.map +1 -0
  147. package/dist/readiness.js +27 -0
  148. package/dist/reads.d.ts +36 -0
  149. package/dist/reads.d.ts.map +1 -0
  150. package/dist/reads.js +136 -0
  151. package/dist/recovery.d.ts +26 -0
  152. package/dist/recovery.d.ts.map +1 -0
  153. package/dist/recovery.js +232 -0
  154. package/dist/refresh.d.ts +21 -7
  155. package/dist/refresh.d.ts.map +1 -1
  156. package/dist/refresh.js +155 -55
  157. package/dist/replica.d.ts +58 -33
  158. package/dist/replica.d.ts.map +1 -1
  159. package/dist/replica.js +355 -158
  160. package/dist/room-draft.d.ts +62 -11
  161. package/dist/room-draft.d.ts.map +1 -1
  162. package/dist/room-draft.js +281 -83
  163. package/dist/room-drafts.d.ts +29 -0
  164. package/dist/room-drafts.d.ts.map +1 -0
  165. package/dist/room-drafts.js +216 -0
  166. package/dist/rows.d.ts +32 -34
  167. package/dist/rows.d.ts.map +1 -1
  168. package/dist/rows.js +65 -87
  169. package/dist/save.d.ts +74 -6
  170. package/dist/save.d.ts.map +1 -1
  171. package/dist/save.js +902 -182
  172. package/dist/saves.d.ts +96 -0
  173. package/dist/saves.d.ts.map +1 -0
  174. package/dist/saves.js +999 -0
  175. package/dist/search.d.ts +15 -54
  176. package/dist/search.d.ts.map +1 -1
  177. package/dist/search.js +10 -121
  178. package/dist/seed.d.ts +30 -0
  179. package/dist/seed.d.ts.map +1 -0
  180. package/dist/seed.js +141 -0
  181. package/dist/shared.d.ts +15 -0
  182. package/dist/shared.d.ts.map +1 -0
  183. package/dist/shared.js +421 -0
  184. package/dist/socket/admission.d.ts +7 -0
  185. package/dist/socket/admission.d.ts.map +1 -0
  186. package/dist/socket/admission.js +39 -0
  187. package/dist/socket/answers.d.ts +6 -0
  188. package/dist/socket/answers.d.ts.map +1 -0
  189. package/dist/socket/answers.js +20 -0
  190. package/dist/socket/reads.d.ts +10 -0
  191. package/dist/socket/reads.d.ts.map +1 -0
  192. package/dist/socket/reads.js +31 -0
  193. package/dist/socket.d.ts +43 -0
  194. package/dist/socket.d.ts.map +1 -0
  195. package/dist/socket.js +96 -0
  196. package/dist/source.d.ts +61 -0
  197. package/dist/source.d.ts.map +1 -0
  198. package/dist/source.js +81 -0
  199. package/dist/status.d.ts +7 -4
  200. package/dist/status.d.ts.map +1 -1
  201. package/dist/status.js +3 -15
  202. package/dist/store/fences.d.ts +16 -0
  203. package/dist/store/fences.d.ts.map +1 -0
  204. package/dist/store/fences.js +26 -0
  205. package/dist/store/installs.d.ts +30 -0
  206. package/dist/store/installs.d.ts.map +1 -0
  207. package/dist/store/installs.js +34 -0
  208. package/dist/store/records.d.ts +212 -0
  209. package/dist/store/records.d.ts.map +1 -0
  210. package/dist/store/records.js +408 -0
  211. package/dist/store/room-held.d.ts +8 -0
  212. package/dist/store/room-held.d.ts.map +1 -0
  213. package/dist/store/room-held.js +17 -0
  214. package/dist/store/watchers.d.ts +15 -0
  215. package/dist/store/watchers.d.ts.map +1 -0
  216. package/dist/store/watchers.js +36 -0
  217. package/dist/store/writes.d.ts +24 -0
  218. package/dist/store/writes.d.ts.map +1 -0
  219. package/dist/store/writes.js +125 -0
  220. package/dist/store.d.ts +77 -94
  221. package/dist/store.d.ts.map +1 -1
  222. package/dist/store.js +118 -389
  223. package/dist/stored.d.ts +156 -0
  224. package/dist/stored.d.ts.map +1 -0
  225. package/dist/stored.js +637 -0
  226. package/dist/tables.d.ts +407 -0
  227. package/dist/tables.d.ts.map +1 -0
  228. package/dist/tables.js +724 -0
  229. package/dist/tallies.d.ts +18 -0
  230. package/dist/tallies.d.ts.map +1 -0
  231. package/dist/tallies.js +129 -0
  232. package/dist/transfer.d.ts +0 -5
  233. package/dist/transfer.d.ts.map +1 -1
  234. package/dist/turn.d.ts +9 -0
  235. package/dist/turn.d.ts.map +1 -0
  236. package/dist/turn.js +75 -0
  237. package/dist/validation.d.ts +29 -0
  238. package/dist/validation.d.ts.map +1 -0
  239. package/dist/validation.js +104 -0
  240. package/dist/windows.d.ts +11 -0
  241. package/dist/windows.d.ts.map +1 -0
  242. package/dist/windows.js +16 -0
  243. package/dist/wire.d.ts +205 -19
  244. package/dist/wire.d.ts.map +1 -1
  245. package/dist/wire.js +95 -61
  246. package/package.json +33 -10
  247. package/AGENTS.md +0 -207
  248. package/dist/attachment.d.ts +0 -3
  249. package/dist/attachment.d.ts.map +0 -1
  250. package/dist/attachment.js +0 -8
  251. package/dist/read-side.d.ts +0 -14
  252. package/dist/read-side.d.ts.map +0 -1
  253. package/dist/read-side.js +0 -371
package/dist/memory.js CHANGED
@@ -1,9 +1,12 @@
1
- import { ROOM_CONTRACT_VERSION, } from './adapter.js';
2
- import { MD_MODEL } from './collection.js';
1
+ import { documentFacts, parse } from '@agentshouse/mdmodel';
2
+ import { NO_ROOM_TYPES, schemaTypes } from './attached-model.js';
3
3
  import { sameChange } from './overlay.js';
4
+ const ATTACHMENTS = 'attachments';
5
+ function lastName(path) {
6
+ return path.slice(path.lastIndexOf('/') + 1);
7
+ }
4
8
  const FIXTURE_RETENTION = {
5
9
  history: { kind: 'duration', seconds: 1 },
6
- outcome: 1,
7
10
  refreshHorizon: 1,
8
11
  };
9
12
  function declaredRetention(value) {
@@ -15,6 +18,14 @@ function declaredRetention(value) {
15
18
  }
16
19
  throw new Error('history retention duration must be a finite positive number of seconds');
17
20
  }
21
+ const MODEL_ABSENT = { kind: 'model-unavailable', reason: 'not-readable' };
22
+ function modelsOf(room) {
23
+ return { collection: room.model ?? MODEL_ABSENT };
24
+ }
25
+ function changedModels(room, from) {
26
+ const now = modelsOf(room);
27
+ return JSON.stringify(now) === JSON.stringify(modelsOf(from)) ? undefined : now;
28
+ }
18
29
  function named(source, paths) {
19
30
  const taken = {};
20
31
  for (const path of paths) {
@@ -31,8 +42,6 @@ async function resolveScript(answer, fallback, request) {
31
42
  }
32
43
  export function createMemoryAdapter(options = {}) {
33
44
  let rooms = options.rooms === undefined ? [] : [...options.rooms];
34
- const mdModel = options.mdModel ?? MD_MODEL;
35
- const supportedVersions = options.supportedVersions ?? [ROOM_CONTRACT_VERSION];
36
45
  const capabilities = options.capabilities ?? [];
37
46
  const providerLimits = options.providerLimits ?? [];
38
47
  const retention = declaredRetention(options.retention);
@@ -44,11 +53,16 @@ export function createMemoryAdapter(options = {}) {
44
53
  const listeners = new Set();
45
54
  const modeObservers = new Set();
46
55
  const scripted = new Map();
47
- const unsealed = new Map();
48
- const sealed = new Set();
56
+ const sentTurns = new Map();
49
57
  const historyPageSize = options.historyPageSize;
50
- let selected = [];
58
+ const attached = new Map();
59
+ const linked = new WeakMap();
60
+ const carried = new Map();
61
+ let expiring = [];
62
+ let openings = 0;
63
+ const declaredRooms = new Set();
51
64
  let replicated = [];
65
+ let heldCursor = null;
52
66
  let issued = 0;
53
67
  function scriptConversations(entries) {
54
68
  scripted.clear();
@@ -76,8 +90,48 @@ export function createMemoryAdapter(options = {}) {
76
90
  const wanted = ordinals.get(`${locator}\u0000${target}`);
77
91
  return held !== undefined && wanted !== undefined && held >= wanted;
78
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
+ }
79
131
  function retain(entries) {
80
132
  for (const room of entries) {
133
+ for (const [path, content] of Object.entries(room.documents ?? {}))
134
+ factsAt(room, path, content);
81
135
  note(room.locator, room);
82
136
  const versions = served.get(room.locator) ?? new Map();
83
137
  versions.set(room.roomVersion, room);
@@ -89,42 +143,64 @@ export function createMemoryAdapter(options = {}) {
89
143
  return {
90
144
  name: room.name,
91
145
  locator: room.locator,
146
+ handle: room.handle,
92
147
  roomVersion: room.roomVersion,
93
148
  operations: room.operations,
94
149
  replica: room.replica,
95
- attachedModel: room.attachedModel,
96
150
  ...(room.typedSelection === undefined ? {} : { typedSelection: room.typedSelection }),
97
151
  providerOwnedPaths: room.providerOwnedPaths,
98
152
  };
99
153
  }
100
- function page(room, from) {
154
+ function page(room, after) {
101
155
  const paths = Object.keys(room.documents ?? {}).sort();
156
+ const opening = after === undefined;
157
+ const from = opening ? 0 : paths.filter((path) => path <= after).length;
102
158
  const size = pageSize ?? paths.length;
103
159
  const taken = paths.slice(from, from + size);
104
160
  const next = from + taken.length;
105
- const targets = from === 0 ? (room.binaryTargets ?? []) : [];
161
+ const targets = opening ? Object.keys(room.binaryFiles ?? {}) : [];
106
162
  return {
107
163
  kind: 'bootstrap',
108
164
  roomVersion: room.roomVersion,
165
+ ...(opening ? { models: modelsOf(room) } : {}),
109
166
  documents: named(room.documents ?? {}, taken),
110
- revisions: named(room.revisions ?? {}, taken),
167
+ facts: factsIn(room, taken),
168
+ revisions: named(room.revisions ?? {}, [...taken, ...targets]),
169
+ ...(room.fileIds === undefined
170
+ ? {}
171
+ : { fileIds: named(room.fileIds, [...taken, ...targets]) }),
172
+ ...(room.created === undefined
173
+ ? {}
174
+ : { created: named(room.created, [...taken, ...targets]) }),
111
175
  changes: named(room.changes ?? {}, [...taken, ...targets]),
112
- binaryTargets: targets,
113
- ...(from === 0 && room.attachments !== undefined ? { attachments: room.attachments } : {}),
114
- ...(next < paths.length ? { pageToken: String(next) } : {}),
176
+ binaryFiles: named(room.binaryFiles ?? {}, targets),
177
+ ...(opening && room.folders !== undefined ? { folders: room.folders } : {}),
178
+ ...(next < paths.length ? { pageToken: taken.at(-1) } : {}),
179
+ };
180
+ }
181
+ function foldersMoved(room, from) {
182
+ const now = new Set(room.folders ?? []);
183
+ const before = new Set(from.folders ?? []);
184
+ const folders = [...now].filter((folder) => !before.has(folder)).sort();
185
+ const folderRemovals = [...before].filter((folder) => !now.has(folder)).sort();
186
+ return {
187
+ ...(folders.length === 0 ? {} : { folders }),
188
+ ...(folderRemovals.length === 0 ? {} : { folderRemovals }),
115
189
  };
116
190
  }
117
- function advancement(room, from, at) {
191
+ function advancement(room, from, at, whole = false) {
118
192
  const current = room.documents ?? {};
119
193
  const previous = from.documents ?? {};
120
- const currentTargets = new Set(room.binaryTargets ?? []);
121
- const previousTargets = new Set(from.binaryTargets ?? []);
194
+ const currentTargets = new Set(Object.keys(room.binaryFiles ?? {}));
195
+ const previousTargets = new Set(Object.keys(from.binaryFiles ?? {}));
122
196
  const headMoved = (path) => room.revisions?.[path] !== from.revisions?.[path] ||
197
+ room.fileIds?.[path] !== from.fileIds?.[path] ||
198
+ room.created?.[path] !== from.created?.[path] ||
123
199
  !sameChange(room.changes?.[path], from.changes?.[path]);
124
200
  const changed = Object.keys(current)
125
201
  .filter((path) => current[path] !== previous[path] || headMoved(path))
126
202
  .sort();
127
- const size = pageSize ?? changed.length;
203
+ const size = whole ? changed.length : (pageSize ?? changed.length);
128
204
  const taken = changed.slice(at, at + size);
129
205
  const next = at + taken.length;
130
206
  const opening = at === 0;
@@ -134,25 +210,52 @@ export function createMemoryAdapter(options = {}) {
134
210
  const targets = opening
135
211
  ? [...currentTargets].filter((target) => !previousTargets.has(target) || headMoved(target))
136
212
  : [];
137
- const attachments = room.attachments ?? [];
138
- const declared = opening && JSON.stringify(attachments) !== JSON.stringify(from.attachments ?? []);
213
+ const models = opening ? changedModels(room, from) : undefined;
139
214
  return {
140
215
  kind: 'net-advancement',
141
216
  roomVersion: room.roomVersion,
217
+ ...(models === undefined ? {} : { models }),
142
218
  documents: named(current, taken),
143
- revisions: named(room.revisions ?? {}, taken),
219
+ facts: factsIn(room, taken),
220
+ revisions: named(room.revisions ?? {}, [...taken, ...targets]),
221
+ ...(room.fileIds === undefined
222
+ ? {}
223
+ : { fileIds: named(room.fileIds, [...taken, ...targets]) }),
224
+ ...(room.created === undefined
225
+ ? {}
226
+ : { created: named(room.created, [...taken, ...targets]) }),
144
227
  changes: named(room.changes ?? {}, [...taken, ...targets]),
145
- binaryTargets: targets,
228
+ binaryFiles: named(room.binaryFiles ?? {}, targets),
229
+ ...(opening ? foldersMoved(room, from) : {}),
146
230
  removals: opening
147
231
  ? [
148
232
  ...Object.keys(previous).filter((path) => !(path in current) && !currentTargets.has(path)),
149
233
  ...[...previousTargets].filter((target) => !currentTargets.has(target) && !(target in current)),
150
234
  ].sort()
151
235
  : [],
152
- ...(declared ? { attachments } : {}),
153
236
  ...(pageToken === undefined ? {} : { pageToken }),
154
237
  };
155
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
+ }
156
259
  function pageOf(conversation, after) {
157
260
  const messages = conversation.messages ?? [];
158
261
  const first = typeof after === 'string' ? messages.findIndex((message) => message.position > after) : 0;
@@ -167,16 +270,13 @@ export function createMemoryAdapter(options = {}) {
167
270
  : {}),
168
271
  };
169
272
  }
170
- function historiesOf(input) {
171
- const asked = input?.conversations;
172
- if (!Array.isArray(asked))
173
- return { kind: 'rejected', code: 'invalid_request', fields: {} };
273
+ function historiesOf(request) {
174
274
  const histories = [];
175
- for (const entry of asked) {
176
- 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);
177
277
  if (conversation === undefined)
178
278
  continue;
179
- const outcome = conversation.outcomes?.['conversation.history'];
279
+ const outcome = conversation.outcomes?.history;
180
280
  if (outcome !== undefined)
181
281
  return outcome;
182
282
  histories.push(pageOf(conversation, entry.after));
@@ -187,52 +287,94 @@ export function createMemoryAdapter(options = {}) {
187
287
  function conversationAction(request) {
188
288
  const name = request.name;
189
289
  const input = (request.input ?? {});
190
- if (name === 'conversation.history')
191
- return historiesOf(request.input);
192
- if (name === 'conversation.create') {
290
+ if (name === 'conversations/create') {
193
291
  issued += 1;
194
292
  const conversation = `conversation-${issued}`;
195
293
  scripted.set(conversation, { conversation });
196
- return { kind: 'action', result: { conversation } };
294
+ return { kind: 'action', result: { conversation_id: conversation } };
197
295
  }
198
- const named = typeof input.conversation === 'string' ? input.conversation : '';
296
+ const named = typeof input.conversation_id === 'string' ? input.conversation_id : '';
199
297
  const conversation = scripted.get(named);
200
298
  if (conversation === undefined) {
201
- return { kind: 'rejected', code: 'unknown-conversation', fields: { conversation: named } };
299
+ return {
300
+ kind: 'rejected',
301
+ code: 'unknown-conversation',
302
+ fields: { conversation: named },
303
+ retryable: false,
304
+ };
202
305
  }
203
306
  const outcome = conversation.outcomes?.[name];
204
307
  if (outcome !== undefined)
205
308
  return outcome;
206
- if (name === 'conversation.send') {
309
+ if (name === 'conversations/send') {
310
+ const operation = typeof input.operation_id === 'string' ? input.operation_id : null;
311
+ const answered = operation === null ? undefined : sentTurns.get(operation);
312
+ if (answered !== undefined)
313
+ return { kind: 'action', result: { message_id: answered } };
207
314
  issued += 1;
208
315
  const turn = `turn-${issued}`;
209
- unsealed.set(turn, named);
210
- return { kind: 'action', result: { turn } };
211
- }
212
- if (name === 'conversation.cancel') {
213
- const turn = typeof input.turn === 'string' ? input.turn : '';
214
- if (sealed.has(turn) || !unsealed.has(turn)) {
215
- return { kind: 'rejected', code: 'turn-sealed', fields: { turn } };
216
- }
217
- unsealed.delete(turn);
218
- return { kind: 'action', result: { turn, cancelled: true } };
316
+ if (operation !== null)
317
+ sentTurns.set(operation, turn);
318
+ return { kind: 'action', result: { message_id: turn } };
219
319
  }
220
- if (name === 'conversation.delete')
320
+ if (name === 'conversations/delete')
221
321
  scripted.delete(named);
222
- return { kind: 'action', result: { conversation: named } };
322
+ return { kind: 'action', result: { conversation_id: named } };
323
+ }
324
+ function announceList() {
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
+ }
223
331
  }
224
332
  function discovery() {
225
333
  return {
226
334
  kind: 'discovery',
227
- supportedVersions,
228
- mdModel,
229
335
  rooms: rooms.map(declared),
230
336
  capabilities,
231
337
  providerLimits,
232
338
  retention,
339
+ ...(options.member === undefined ? {} : { member: options.member }),
233
340
  };
234
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
+ }
235
375
  return {
376
+ ...(options.roomTypes === undefined ? {} : { roomTypes: options.roomTypes }),
377
+ ...(options.typedPaths === undefined ? {} : { typedPaths: options.typedPaths }),
236
378
  setRooms(next) {
237
379
  rooms = [...next];
238
380
  retain(rooms);
@@ -240,15 +382,38 @@ export function createMemoryAdapter(options = {}) {
240
382
  setConversations(next) {
241
383
  scriptConversations(next);
242
384
  },
243
- notifyRoomVersion(notification) {
244
- for (const observer of observers)
245
- observer(notification);
385
+ open() {
386
+ carried.clear();
387
+ announceList();
388
+ opened(emitAll);
389
+ },
390
+ close({ revoked = false, lost = !revoked, authorityChanged = false } = {}) {
391
+ openings += 1;
392
+ carried.clear();
393
+ emitAll({ kind: 'closed', revoked, lost, authorityChanged });
394
+ },
395
+ expire(locators) {
396
+ expiring = [...locators];
397
+ },
398
+ notifyRoomVersion({ locator }) {
399
+ carry(locator);
400
+ },
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
+ });
246
415
  },
247
416
  notifyConversation(event) {
248
- if (event.kind === 'accepted-message') {
249
- sealed.add(event.message.turn);
250
- unsealed.delete(event.message.turn);
251
- }
252
417
  for (const listener of listeners)
253
418
  listener(event);
254
419
  },
@@ -256,22 +421,32 @@ export function createMemoryAdapter(options = {}) {
256
421
  for (const observer of modeObservers)
257
422
  observer(mode);
258
423
  },
259
- selectedRooms() {
260
- return selected;
424
+ heldRooms() {
425
+ return [...carried.keys()].sort();
261
426
  },
262
427
  replicatedConversations() {
263
428
  return replicated;
264
429
  },
430
+ heldConversationCursor() {
431
+ return heldCursor;
432
+ },
265
433
  conversations: {
266
434
  replicate(next) {
267
435
  replicated = [...next];
268
436
  },
437
+ hold(cursor) {
438
+ heldCursor = cursor;
439
+ },
269
440
  observe(notify) {
270
441
  listeners.add(notify);
271
442
  return () => {
272
443
  listeners.delete(notify);
273
444
  };
274
445
  },
446
+ history: (request) => Promise.resolve(historiesOf(request)),
447
+ ...(options.agents === undefined
448
+ ? {}
449
+ : { agents: () => Promise.resolve(structuredClone(options.agents)) }),
275
450
  },
276
451
  serviceMode: {
277
452
  observe(notify) {
@@ -281,12 +456,49 @@ export function createMemoryAdapter(options = {}) {
281
456
  };
282
457
  },
283
458
  },
284
- roomVersions: {
285
- select(locators) {
286
- selected = [...locators];
459
+ roomStream: {
460
+ hold(asked) {
461
+ const named = asked.map((room) => room.locator);
462
+ const unknown = asked.flatMap((room) => {
463
+ const current = rooms.find((entry) => entry.locator === room.locator);
464
+ const version = room.roomVersion;
465
+ return current === undefined ||
466
+ (version !== null &&
467
+ (current.roomVersion === version || served.get(room.locator)?.has(version) === true))
468
+ ? []
469
+ : [room.locator];
470
+ });
471
+ const expired = [
472
+ ...new Set([...expiring.filter((locator) => named.includes(locator)), ...unknown]),
473
+ ];
474
+ expiring = expiring.filter((locator) => !named.includes(locator));
475
+ queueMicrotask(() => {
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
+ }
483
+ }
484
+ emitAll({ kind: 'caught-up', expired });
485
+ });
287
486
  },
288
487
  observe(notify) {
289
488
  observers.add(notify);
489
+ queueMicrotask(() => {
490
+ if (!observers.has(notify))
491
+ return;
492
+ if (options.unopened === true) {
493
+ notify({ kind: 'closed', revoked: false, lost: true, authorityChanged: false });
494
+ return;
495
+ }
496
+ announceList();
497
+ opened((event) => {
498
+ if (observers.has(notify))
499
+ notify(event);
500
+ });
501
+ });
290
502
  return () => {
291
503
  observers.delete(notify);
292
504
  };
@@ -298,32 +510,28 @@ export function createMemoryAdapter(options = {}) {
298
510
  else
299
511
  scripts[operation] = answer;
300
512
  },
301
- async model(_authorization, request) {
302
- return resolveScript(scripts.model, () => {
303
- const room = rooms.find((entry) => entry.locator === request.locator);
304
- const answer = request.attached === 'collection' ? room?.model : room?.selectionModel;
305
- return answer ?? { kind: 'model-unavailable', reason: 'not-readable' };
306
- }, request);
307
- },
308
513
  async authorize() {
309
514
  return resolveScript(scripts.authorize, () => ({}), undefined);
310
515
  },
311
516
  async discover(_authorization) {
312
- return resolveScript(scripts.discover, discovery, undefined).then((answer) => {
313
- if (answer.kind === 'discovery')
314
- for (const room of answer.rooms)
315
- note(room.locator, room);
316
- return answer;
317
- });
517
+ return discovered();
318
518
  },
319
519
  reached,
520
+ follows(locator, roomVersion, next) {
521
+ const scripted = scripts.follows;
522
+ if (typeof scripted === 'boolean')
523
+ return scripted;
524
+ if (scripted !== undefined)
525
+ return scripted(locator, roomVersion, next);
526
+ return true;
527
+ },
320
528
  async bootstrap(_authorization, request) {
321
529
  return resolveScript(scripts.bootstrap, () => {
322
530
  const room = rooms.find((entry) => entry.locator === request.locator);
323
531
  if (room === undefined) {
324
532
  return { kind: 'room-not-readable', locator: request.locator };
325
533
  }
326
- return page(room, request.pageToken === undefined ? 0 : Number(request.pageToken));
534
+ return page(room, request.pageToken);
327
535
  }, request).then((answer) => note(request.locator, answer));
328
536
  },
329
537
  async refresh(_authorization, request) {
@@ -341,10 +549,6 @@ export function createMemoryAdapter(options = {}) {
341
549
  }
342
550
  return advancement(continuation.room, continuation.from, continuation.at);
343
551
  }
344
- if (request.minPosition !== undefined &&
345
- !reached(room.locator, room.roomVersion, request.minPosition)) {
346
- return { kind: 'retryable-refusal', code: 'replica_behind', fields: {} };
347
- }
348
552
  if (request.fromRoomVersion === room.roomVersion) {
349
553
  return { kind: 'still-current' };
350
554
  }
@@ -360,8 +564,20 @@ export function createMemoryAdapter(options = {}) {
360
564
  if (room === undefined) {
361
565
  return { kind: 'room-not-readable', locator: request.locator };
362
566
  }
363
- return { kind: 'rejected', code: 'unscripted-mutation', fields: {}, affected: {} };
364
- }, request).then((answer) => note(request.locator, answer));
567
+ return {
568
+ kind: 'rejected',
569
+ code: 'unscripted-mutation',
570
+ fields: {},
571
+ retryable: false,
572
+ affected: {},
573
+ };
574
+ }, request).then((answer) => {
575
+ const destination = request.changeBatch.between?.destination.room;
576
+ if (answer.kind === 'accepted' && answer.destination !== undefined && destination) {
577
+ note(destination, answer.destination);
578
+ }
579
+ return note(request.locator, answer);
580
+ });
365
581
  },
366
582
  async history(_authorization, request) {
367
583
  return resolveScript(scripts.history, () => {
@@ -375,11 +591,46 @@ export function createMemoryAdapter(options = {}) {
375
591
  };
376
592
  }, request);
377
593
  },
594
+ async binary(_authorization, request) {
595
+ return resolveScript(scripts.binary, () => {
596
+ const room = rooms.find((entry) => entry.locator === request.locator);
597
+ if (room === undefined)
598
+ return { kind: 'room-not-readable', locator: request.locator };
599
+ const blob = heldBlob(room, request.path);
600
+ return blob === undefined
601
+ ? { kind: 'rejected', code: 'file_not_found', fields: {}, retryable: false }
602
+ : { kind: 'binary', body: blob.stream(), mediaType: blob.type };
603
+ }, request);
604
+ },
605
+ async link(_authorization, 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);
628
+ },
378
629
  async action(_authorization, request) {
379
630
  return resolveScript(scripts.action, () => {
380
- if (request.name.startsWith('conversation.'))
631
+ if (request.name.startsWith('conversations/'))
381
632
  return conversationAction(request);
382
- return { kind: 'rejected', code: 'unscripted-action', fields: {} };
633
+ return { kind: 'rejected', code: 'unscripted-action', fields: {}, retryable: false };
383
634
  }, request).then((answer) => {
384
635
  if (answer.kind === 'action' && answer.at !== undefined)
385
636
  note(answer.at.locator, answer.at);