@agentshouse/mdruntime-adapter 0.1.0-cd028205a5103 → 0.1.0-cd61dfca5d699

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 (50) hide show
  1. package/AGENTS.md +277 -222
  2. package/dist/actions.d.ts +62 -1
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +156 -133
  5. package/dist/adapter.d.ts +1 -0
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/adapter.js +317 -93
  8. package/dist/discovery.d.ts +4 -2
  9. package/dist/discovery.d.ts.map +1 -1
  10. package/dist/discovery.js +22 -7
  11. package/dist/gather.d.ts +1 -0
  12. package/dist/gather.d.ts.map +1 -1
  13. package/dist/gather.js +11 -6
  14. package/dist/handed.d.ts +15 -0
  15. package/dist/handed.d.ts.map +1 -0
  16. package/dist/handed.js +38 -0
  17. package/dist/index.d.ts +2 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -0
  20. package/dist/model.d.ts +1 -1
  21. package/dist/model.d.ts.map +1 -1
  22. package/dist/model.js +6 -2
  23. package/dist/read.d.ts +34 -7
  24. package/dist/read.d.ts.map +1 -1
  25. package/dist/read.js +156 -77
  26. package/dist/referrer.d.ts +7 -0
  27. package/dist/referrer.d.ts.map +1 -0
  28. package/dist/referrer.js +35 -0
  29. package/dist/refusal.d.ts +2 -5
  30. package/dist/refusal.d.ts.map +1 -1
  31. package/dist/refusal.js +6 -9
  32. package/dist/site.d.ts +1 -1
  33. package/dist/site.d.ts.map +1 -1
  34. package/dist/site.js +182 -49
  35. package/dist/stream.d.ts +3 -2
  36. package/dist/stream.d.ts.map +1 -1
  37. package/dist/stream.js +326 -16
  38. package/dist/transport.d.ts +64 -10
  39. package/dist/transport.d.ts.map +1 -1
  40. package/dist/transport.js +3 -0
  41. package/dist/version.d.ts +1 -0
  42. package/dist/version.d.ts.map +1 -1
  43. package/dist/version.js +1 -1
  44. package/dist/write.d.ts +3 -9
  45. package/dist/write.d.ts.map +1 -1
  46. package/dist/write.js +92 -14
  47. package/package.json +3 -4
  48. package/dist/links.d.ts +0 -20
  49. package/dist/links.d.ts.map +0 -1
  50. package/dist/links.js +0 -104
package/dist/adapter.js CHANGED
@@ -1,23 +1,52 @@
1
1
  import { DISCOVER_PREFIXES, capabilities, discoveredRoom, mapDiscovery, parseDiscovery, trackedFrom, } from './discovery.js';
2
- import { scopeModels, streamedModels, viewModels } from './model.js';
3
- import { gathered, selections } from './gather.js';
2
+ import { partModels, streamedModels, viewModels } from './model.js';
3
+ import { gathered, gatheredEach, selections } from './gather.js';
4
4
  import { createSiteAdapter } from './site.js';
5
- import { bootstrapOf, contentPrefixes, matchesScope, parseEnumeration, parseEnvelope, parseGrant, refreshFrom, viewOf, } from './read.js';
6
- import { acceptedLinks, enumeratedLinks, heldLinks, scopeLinks, streamedLinks, } from './links.js';
5
+ import { bootstrapOf, contentPrefixes, matchesScope, parseEnumeration, parseEnvelope, parseGrant, refreshFrom, viewOf, wholeView, } from './read.js';
6
+ import { sha256Hex } from './digest.js';
7
+ import { acceptedHanded, enumeratedHanded, heldHanded, scopeHanded, streamedHanded, } from './handed.js';
7
8
  import { houseAction } from './actions.js';
8
9
  import { classifyDoor, classifyRead, discoveryFailure, doorRefusal } from './refusal.js';
9
10
  import { houseNotifications } from './stream.js';
10
- import { DOOR_BATCH, DOOR_BATCH_UPLOAD, DOOR_BOOTSTRAP, DOOR_DISCOVER, DOOR_ENUMERATE, DOOR_REVISIONS, } from './transport.js';
11
+ import { DOOR_BATCH, DOOR_BATCH_UPLOAD, DOOR_BOOTSTRAP, DOOR_DISCOVER, DOOR_ENUMERATE, DOOR_FILES_GET, DOOR_REVISIONS, } from './transport.js';
11
12
  import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
13
+ import { houseReferrer } from './referrer.js';
12
14
  import { followsVersion, logPosition, presented, reachedVersion, roomVersion, } from './version.js';
13
15
  const UNCERTAIN = { kind: 'transport-uncertainty' };
14
16
  const UNCERTAIN_READ = { kind: 'transport-uncertainty' };
15
17
  function lapsedGrant(outcome) {
16
18
  return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
17
19
  }
20
+ function heldFiles(authority, held, after, budget) {
21
+ if (held === undefined || budget === 0)
22
+ return null;
23
+ const paths = Object.keys(held.revisions)
24
+ .filter((path) => held.documents[path] !== undefined && (after === null || path > after))
25
+ .sort();
26
+ if (paths.length === 0)
27
+ return null;
28
+ const files = paths.slice(0, budget).map((path) => ({ path, revision: held.revisions[path] }));
29
+ return { room: authority, through: paths.length > budget ? files.at(-1).path : null, files };
30
+ }
31
+ async function reused(scope, held) {
32
+ const sent = new Set(scope.contents.map((entry) => entry.path));
33
+ const contents = [...scope.contents];
34
+ for (const file of scope.files) {
35
+ if (file.binary !== undefined || sent.has(file.path))
36
+ continue;
37
+ const content = held?.documents[file.path];
38
+ if (content === undefined ||
39
+ held?.revisions[file.path] !== file.revision ||
40
+ (await sha256Hex(content)) !== file.sha256) {
41
+ return null;
42
+ }
43
+ contents.push({ path: file.path, content });
44
+ }
45
+ return { ...scope, contents };
46
+ }
18
47
  export function createAgentsHouseAdapter(options) {
19
48
  if ('site' in options)
20
- return createSiteAdapter(options.site);
49
+ return createSiteAdapter(options.site, options.snapshot === true);
21
50
  const transport = options.transport;
22
51
  const notifications = houseNotifications(transport, 'credential');
23
52
  const tracked = new Map();
@@ -28,38 +57,80 @@ export function createAgentsHouseAdapter(options) {
28
57
  throw new Error('no Discovery declared the bounds');
29
58
  return bounds;
30
59
  }
31
- async function bootstrappedRoom(envelope, entry) {
32
- const scope = envelope.scopes.find((candidate) => matchesScope(candidate, entry));
33
- if (scope === undefined)
60
+ async function bootstrappedRoom(envelope, sent, asked, continued) {
61
+ const { entry } = asked;
62
+ if (sent === undefined)
34
63
  return { kind: 'room-not-readable', locator: entry.locator };
35
- handed.set(entry.locator, scopeLinks(scope));
64
+ const scope = await reused(sent, asked.held);
65
+ if (scope === null) {
66
+ return asked.held === undefined
67
+ ? UNCERTAIN_READ
68
+ : continued === null
69
+ ? bootstrapTogether({ entry, held: undefined })
70
+ : continuedBootstrap({ entry, held: undefined }, continued);
71
+ }
72
+ const cursor = envelope.next?.room === entry.authority ? envelope.next : undefined;
73
+ const next = cursor?.path ?? null;
74
+ if (continued === null && next === null)
75
+ handed.set(entry.locator, scopeHanded(scope));
76
+ else
77
+ handed.delete(entry.locator);
36
78
  notifications.reach(entry.authority, scope);
37
- const models = await scopeModels(scope, entry);
38
- return { ...bootstrapOf(scope), ...(models === undefined ? {} : { models }) };
79
+ const seen = [
80
+ ...(continued?.seen ?? []),
81
+ ...entry.schemas.filter((schema) => scope.files.some((file) => file.path === schema)),
82
+ ];
83
+ const models = await partModels(scope, entry, new Set(seen), next === null);
84
+ const continuation = next === null
85
+ ? null
86
+ : { after: next, seen, ...(cursor?.cut === undefined ? {} : { cut: cursor.cut }) };
87
+ return {
88
+ ...bootstrapOf(scope),
89
+ ...(models === undefined ? {} : { models }),
90
+ ...(continuation === null ? {} : { pageToken: JSON.stringify(continuation) }),
91
+ };
92
+ }
93
+ async function enumeratedPage(ask, page) {
94
+ const answer = await transport.door(DOOR_ENUMERATE, {
95
+ prefixes: contentPrefixes(ask.entry),
96
+ positions: [
97
+ {
98
+ ...presented(ask.entry.authority, { position: page.after, logEpoch: ask.from.logEpoch }),
99
+ page: { through: page.through, read: page.read },
100
+ },
101
+ ],
102
+ });
103
+ const failure = classifyRead(answer.status, answer.body, ask.entry.locator);
104
+ if (failure !== null)
105
+ return failure;
106
+ const enumeration = parseEnumeration(answer.body, 'credential');
107
+ if (enumeration === null)
108
+ return UNCERTAIN_READ;
109
+ return (viewOf(enumeration, ask.entry) ?? { kind: 'room-not-readable', locator: ask.entry.locator });
39
110
  }
40
111
  async function enumeratedRoom(enumeration, ask) {
41
- const view = viewOf(enumeration, ask.entry);
42
- if (view === undefined)
112
+ const first = viewOf(enumeration, ask.entry);
113
+ if (first === undefined)
43
114
  return { kind: 'room-not-readable', locator: ask.entry.locator };
115
+ const view = await wholeView(first, (page) => enumeratedPage(ask, page));
116
+ if ('kind' in view)
117
+ return view;
44
118
  notifications.reach(ask.entry.authority, view);
45
119
  const answer = refreshFrom(view, ask.fromRoomVersion);
46
120
  if (answer.kind !== 'net-advancement')
47
121
  return answer;
48
- handed.set(ask.entry.locator, enumeratedLinks(view, handed.get(ask.entry.locator)));
122
+ handed.set(ask.entry.locator, enumeratedHanded(view, handed.get(ask.entry.locator)));
49
123
  const models = await viewModels(view, ask.entry);
50
124
  return models === undefined ? answer : { ...answer, models };
51
125
  }
52
126
  async function streamed(entry, request, from) {
53
- const links = handed.get(entry.locator);
54
- if (links?.roomVersion !== request.fromRoomVersion)
127
+ const held = handed.get(entry.locator);
128
+ if (held?.roomVersion !== request.fromRoomVersion)
55
129
  return null;
56
- const advanced = notifications.advance(entry, from, (path, revision) => links.revisions.get(path) === revision);
130
+ const advanced = notifications.advance(entry, from, (path, revision) => held.revisions.get(path) === revision);
57
131
  if (advanced === null)
58
132
  return null;
59
- const relinked = streamedLinks(links, advanced);
60
- if (relinked === null)
61
- return null;
62
- handed.set(entry.locator, relinked);
133
+ handed.set(entry.locator, streamedHanded(held, advanced));
63
134
  const models = await streamedModels(advanced.schemas, entry, 'credential');
64
135
  return models === undefined ? advanced.page : { ...advanced.page, models };
65
136
  }
@@ -69,7 +140,7 @@ export function createAgentsHouseAdapter(options) {
69
140
  const at = logPosition(room.roomVersion);
70
141
  if (entry === undefined || at === null)
71
142
  return [];
72
- handed.set(room.locator, heldLinks(room));
143
+ handed.set(room.locator, heldHanded(room));
73
144
  notifications.reach(entry.authority, at);
74
145
  return [presented(entry.authority, at)];
75
146
  });
@@ -78,19 +149,15 @@ export function createAgentsHouseAdapter(options) {
78
149
  function adopted(entry, outcome) {
79
150
  if (outcome.kind !== 'accepted')
80
151
  return outcome;
81
- const { originals, ...accepted } = outcome;
82
- const links = handed.get(entry.locator);
83
- if (links === undefined)
84
- return accepted;
85
- const follows = followsVersion(links.roomVersion, accepted.roomVersion);
86
- const relinked = acceptedLinks(links, accepted, follows ? accepted.roomVersion : links.roomVersion, originals);
87
- handed.set(entry.locator, relinked.links);
88
- const at = logPosition(accepted.roomVersion);
152
+ const held = handed.get(entry.locator);
153
+ if (held === undefined)
154
+ return outcome;
155
+ const follows = followsVersion(held.roomVersion, outcome.roomVersion);
156
+ handed.set(entry.locator, acceptedHanded(held, outcome, follows ? outcome.roomVersion : held.roomVersion));
157
+ const at = logPosition(outcome.roomVersion);
89
158
  if (follows && at !== null)
90
159
  notifications.reach(entry.authority, at);
91
- return relinked.attachments === undefined
92
- ? accepted
93
- : { ...accepted, attachments: relinked.attachments };
160
+ return outcome;
94
161
  }
95
162
  function authorityFor(locator) {
96
163
  return tracked.get(locator);
@@ -109,88 +176,115 @@ export function createAgentsHouseAdapter(options) {
109
176
  return result;
110
177
  return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
111
178
  }
112
- async function bundled(body) {
113
- const grant = parseGrant(body);
114
- if (grant === null)
115
- return body;
116
- if (grant.method !== 'GET')
117
- return null;
118
- const answer = await transport.transfer({
119
- act: 'local_copy_bundle',
120
- method: 'GET',
121
- url: grant.url,
122
- answer: 'json',
123
- });
124
- return answer.status === 200 ? answer.body : null;
125
- }
126
179
  function weight(entry) {
127
180
  return contentPrefixes(entry).length;
128
181
  }
129
- async function bootstrapped(entries) {
130
- const answers = [];
131
- for (const part of selections(entries, weight, declared().max_prefixes)) {
132
- const answer = await transport.door(DOOR_BOOTSTRAP, {
133
- prefixes: part.flatMap((entry) => contentPrefixes(entry)),
134
- });
135
- const classified = part.map((entry) => classifyDoor(answer.status, answer.body, entry.locator));
136
- const envelope = classified[0] === null ? parseEnvelope(await bundled(answer.body)) : null;
137
- for (const [index, entry] of part.entries()) {
138
- const failure = classified[index];
139
- if (failure === null || failure === undefined) {
140
- answers.push(envelope === null ? UNCERTAIN_READ : await bootstrappedRoom(envelope, entry));
141
- continue;
182
+ function holding(asks, after) {
183
+ const budget = Math.floor(declared().max_held_files / Math.max(1, asks.filter((ask) => ask.held !== undefined).length));
184
+ return asks.flatMap((ask) => heldFiles(ask.entry.authority, ask.held, after, budget) ?? []);
185
+ }
186
+ async function bootstrapped(asks, answer) {
187
+ let open = asks.map((asked, ask) => ({ entry: asked.entry, asked, ask }));
188
+ while (open.length > 0) {
189
+ const unreached = [];
190
+ for (const part of selections(open, ({ entry }) => weight(entry), declared().max_prefixes)) {
191
+ const held = holding(part.map(({ asked }) => asked), null);
192
+ const answered = await transport.door(DOOR_BOOTSTRAP, {
193
+ prefixes: part.flatMap(({ entry }) => contentPrefixes(entry)),
194
+ ...(held.length === 0 ? {} : { held }),
195
+ });
196
+ const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
197
+ const envelope = classified[0] === null ? parseEnvelope(answered.body) : null;
198
+ for (const [index, asked] of part.entries()) {
199
+ const failure = classified[index];
200
+ if (failure !== null && failure !== undefined) {
201
+ answer(asked.ask, failure);
202
+ continue;
203
+ }
204
+ if (envelope === null) {
205
+ answer(asked.ask, UNCERTAIN_READ);
206
+ continue;
207
+ }
208
+ const scope = envelope.scopes.find((candidate) => matchesScope(candidate, asked.entry));
209
+ if (scope === undefined && envelope.next !== undefined) {
210
+ unreached.push(asked);
211
+ continue;
212
+ }
213
+ answer(asked.ask, await bootstrappedRoom(envelope, scope, asked.asked, null));
142
214
  }
143
- answers.push(failure);
144
215
  }
216
+ open = unreached;
145
217
  }
146
- return answers;
218
+ }
219
+ async function continuedBootstrap(asked, continued) {
220
+ const { entry } = asked;
221
+ const after = {
222
+ room: entry.authority,
223
+ path: continued.after,
224
+ ...(continued.cut === undefined ? {} : { cut: continued.cut }),
225
+ };
226
+ const held = holding([asked], continued.after);
227
+ const answered = await transport.door(DOOR_BOOTSTRAP, {
228
+ prefixes: contentPrefixes(entry),
229
+ after,
230
+ ...(held.length === 0 ? {} : { held }),
231
+ });
232
+ const failure = classifyRead(answered.status, answered.body, entry.locator);
233
+ if (failure !== null)
234
+ return failure;
235
+ const envelope = parseEnvelope(answered.body);
236
+ if (envelope === null)
237
+ return UNCERTAIN_READ;
238
+ const scope = envelope.scopes.find((candidate) => matchesScope(candidate, entry));
239
+ return bootstrappedRoom(envelope, scope, asked, continued);
147
240
  }
148
241
  function layered(asks) {
149
242
  const depths = new Map();
150
243
  const layers = [];
151
244
  for (const ask of asks) {
152
- const depth = depths.get(ask.entry.authority) ?? 0;
153
- depths.set(ask.entry.authority, depth + 1);
245
+ const depth = depths.get(ask.ask.entry.authority) ?? 0;
246
+ depths.set(ask.ask.entry.authority, depth + 1);
154
247
  (layers[depth] ??= []).push(ask);
155
248
  }
156
249
  return layers;
157
250
  }
158
251
  async function enumerated(asks) {
159
- const answers = new Map();
160
- const parts = layered(asks).flatMap((layer) => selections(layer, (ask) => weight(ask.entry), declared().max_prefixes));
252
+ const answers = [];
253
+ const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, ({ ask }) => weight(ask.entry), declared().max_prefixes));
161
254
  for (const part of parts) {
162
255
  let open = [...part];
163
256
  while (open.length > 0) {
164
257
  const answer = await transport.door(DOOR_ENUMERATE, {
165
- prefixes: open.flatMap((ask) => contentPrefixes(ask.entry)),
166
- positions: open.map((ask) => presented(ask.entry.authority, ask.from)),
258
+ prefixes: open.flatMap(({ ask }) => contentPrefixes(ask.entry)),
259
+ positions: open.map(({ ask }) => presented(ask.entry.authority, ask.from)),
167
260
  });
168
- const classified = open.map((ask) => classifyRead(answer.status, answer.body, ask.entry.locator));
261
+ const classified = open.map(({ ask }) => classifyRead(answer.status, answer.body, ask.entry.locator));
169
262
  if (classified[0]?.kind === 'beyond-horizon') {
170
263
  const named = doorRefusal(answer.body)?.fields.authority;
171
- const expired = open.filter((ask) => ask.entry.authority === named);
264
+ const expired = open.filter(({ ask }) => ask.entry.authority === named);
172
265
  const answered = expired.length === 0 ? UNCERTAIN_READ : { kind: 'beyond-horizon' };
173
266
  const gone = expired.length === 0 ? open : expired;
174
267
  for (const ask of gone)
175
- answers.set(ask, answered);
268
+ answers[ask.index] = answered;
176
269
  open = open.filter((ask) => !gone.includes(ask));
177
270
  continue;
178
271
  }
179
272
  const enumeration = classified[0] === null ? parseEnumeration(answer.body, 'credential') : null;
180
- for (const [index, ask] of open.entries()) {
181
- const failure = classified[index];
273
+ for (const [answerIndex, { ask, index }] of open.entries()) {
274
+ const failure = classified[answerIndex];
182
275
  if (failure !== null && failure !== undefined) {
183
- answers.set(ask, failure);
276
+ answers[index] = failure;
184
277
  continue;
185
278
  }
186
- answers.set(ask, enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask));
279
+ answers[index] =
280
+ enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask);
187
281
  }
188
282
  open = [];
189
283
  }
190
284
  }
191
- return asks.map((ask) => answers.get(ask) ?? UNCERTAIN_READ);
285
+ return answers;
192
286
  }
193
- const bootstrapTogether = gathered(bootstrapped);
287
+ const bootstrapTogether = gatheredEach(bootstrapped);
194
288
  const enumerateTogether = gathered(enumerated);
195
289
  async function uploaded(head, changes, delta, bytes, entry, handed, keep) {
196
290
  const paths = batchPaths(changes);
@@ -207,9 +301,7 @@ export function createAgentsHouseAdapter(options) {
207
301
  return { outcome: UNCERTAIN };
208
302
  const delivered = await transport.transfer({
209
303
  act: 'authored_batch_upload',
210
- method: 'POST',
211
304
  url: grant.url,
212
- answer: 'json',
213
305
  body: `${grant.operation}\n${delta}`,
214
306
  });
215
307
  if (delivered.status !== 200)
@@ -221,7 +313,30 @@ export function createAgentsHouseAdapter(options) {
221
313
  await keep(upload);
222
314
  return { body: { ...head, paths, upload, runtime: notifications.runtime } };
223
315
  }
224
- async function submit(head, changes, delta, bytes, entry, handed, keep) {
316
+ async function staged(answer, batch) {
317
+ const refusal = doorRefusal(answer.body);
318
+ if (refusal?.code !== 'files_unstaged')
319
+ return null;
320
+ const grants = refusal.fields.upload;
321
+ if (!Array.isArray(grants))
322
+ return false;
323
+ for (const grant of grants) {
324
+ const { url, operation, sha256 } = grant;
325
+ const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === sha256);
326
+ if (typeof url !== 'string' || typeof operation !== 'string' || change === undefined)
327
+ return false;
328
+ const delivered = await transport.transfer({
329
+ act: 'binary_stage',
330
+ url,
331
+ body: change.blob,
332
+ operation,
333
+ });
334
+ if (delivered.status !== 200)
335
+ return false;
336
+ }
337
+ return true;
338
+ }
339
+ async function submit(batch, head, changes, delta, bytes, entry, handed, keep) {
225
340
  const inline = { ...head, changes, runtime: notifications.runtime };
226
341
  const prepared = uploadable(inline, bytes, declared())
227
342
  ? await uploaded(head, changes, delta, bytes, entry, handed, keep)
@@ -229,14 +344,61 @@ export function createAgentsHouseAdapter(options) {
229
344
  if ('outcome' in prepared)
230
345
  return prepared.outcome;
231
346
  const answer = await transport.door(DOOR_BATCH, prepared.body);
232
- return mutationOutcome(answer.status, answer.body, entry.locator, entry.handle);
347
+ const stagedFiles = await staged(answer, batch);
348
+ if (stagedFiles === false)
349
+ return UNCERTAIN;
350
+ const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
351
+ if (stagedFiles === true && (await staged(settled, batch)) !== null)
352
+ return UNCERTAIN;
353
+ return mutationOutcome(settled.status, settled.body, entry.locator, entry.handle);
354
+ }
355
+ function granted(answer, locator) {
356
+ const failure = classifyDoor(answer.status, answer.body, locator);
357
+ if (failure !== null) {
358
+ return failure.kind === 'scoped-refusal'
359
+ ? {
360
+ kind: 'rejected',
361
+ code: failure.code,
362
+ fields: failure.fields,
363
+ retryable: failure.retryable,
364
+ }
365
+ : failure;
366
+ }
367
+ const grant = parseGrant(answer.body);
368
+ const name = answer.body.name;
369
+ if (grant === null || grant.method !== 'GET' || typeof name !== 'string') {
370
+ return UNCERTAIN_READ;
371
+ }
372
+ return { kind: 'link', url: grant.url, name };
373
+ }
374
+ async function downloaded(answer, locator) {
375
+ const link = granted(answer, locator);
376
+ if (link.kind !== 'link')
377
+ return link;
378
+ const read = await transport.download(link.url);
379
+ if (read.status !== 200 || read.blob === null)
380
+ return UNCERTAIN_READ;
381
+ return { kind: 'binary', blob: read.blob };
382
+ }
383
+ function pathGrant(request) {
384
+ const entry = authorityFor(request.locator);
385
+ if (entry === undefined)
386
+ return null;
387
+ return transport.door(DOOR_FILES_GET, {
388
+ room_ref: entry.authority,
389
+ path: request.path,
390
+ revision: request.revision,
391
+ });
233
392
  }
234
393
  return {
235
394
  async authorize() {
236
395
  return options.authorize();
237
396
  },
238
- async discover(_authorization) {
239
- const answer = await transport.door(DOOR_DISCOVER, { prefixes: [...DISCOVER_PREFIXES] });
397
+ async discover(_authorization, scope) {
398
+ const answer = await transport.door(DOOR_DISCOVER, {
399
+ prefixes: [...DISCOVER_PREFIXES],
400
+ ...(scope === undefined ? {} : { rooms: scope.authorities }),
401
+ });
240
402
  const failed = discoveryFailure(answer.status, answer.body);
241
403
  if (failed !== null)
242
404
  return failed;
@@ -245,22 +407,42 @@ export function createAgentsHouseAdapter(options) {
245
407
  return { kind: 'transport-uncertainty' };
246
408
  bounds = document.bounds;
247
409
  const entries = trackedFrom(document);
248
- tracked.clear();
410
+ const covered = scope === undefined
411
+ ? null
412
+ : [
413
+ ...new Set([
414
+ ...[...tracked.values()]
415
+ .filter((entry) => scope.authorities.includes(entry.authority))
416
+ .map((entry) => entry.locator),
417
+ ...entries.map((entry) => entry.locator),
418
+ ]),
419
+ ];
420
+ if (covered === null)
421
+ tracked.clear();
422
+ else
423
+ for (const locator of covered)
424
+ tracked.delete(locator);
249
425
  for (const entry of entries)
250
426
  tracked.set(entry.locator, entry);
251
427
  for (const locator of handed.keys())
252
428
  if (!tracked.has(locator))
253
429
  handed.delete(locator);
254
- notifications.track(entries);
255
- return mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document));
430
+ notifications.track([...tracked.values()]);
431
+ return {
432
+ ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions, [...tracked.values()])),
433
+ member: document.member,
434
+ ...(covered === null ? {} : { covered }),
435
+ };
256
436
  },
257
437
  async bootstrap(_authorization, request) {
258
438
  const entry = authorityFor(request.locator);
259
439
  if (entry === undefined)
260
440
  return { kind: 'room-not-readable', locator: request.locator };
261
- if (request.pageToken !== undefined)
262
- return { kind: 'transport-uncertainty' };
263
- return bootstrapTogether(entry);
441
+ const asked = { entry, held: request.held };
442
+ if (request.pageToken !== undefined) {
443
+ return continuedBootstrap(asked, JSON.parse(request.pageToken));
444
+ }
445
+ return bootstrapTogether(asked);
264
446
  },
265
447
  async refresh(_authorization, request) {
266
448
  const entry = authorityFor(request.locator);
@@ -287,10 +469,11 @@ export function createAgentsHouseAdapter(options) {
287
469
  const delta = deltaBody(mapped.changes);
288
470
  const bytes = deltaBytes(delta);
289
471
  const handed = request.upload;
290
- const outcome = await submit(head, mapped.changes, delta, bytes, entry, handed, keep);
472
+ const batch = request.changeBatch;
473
+ const outcome = await submit(batch, head, mapped.changes, delta, bytes, entry, handed, keep);
291
474
  if (handed === undefined || !lapsedGrant(outcome))
292
475
  return adopted(entry, outcome);
293
- return adopted(entry, await submit(head, mapped.changes, delta, bytes, entry, undefined, keep));
476
+ return adopted(entry, await submit(batch, head, mapped.changes, delta, bytes, entry, undefined, keep));
294
477
  },
295
478
  async history(_authorization, request) {
296
479
  const entry = authorityFor(request.locator);
@@ -307,6 +490,38 @@ export function createAgentsHouseAdapter(options) {
307
490
  ...(request.revision === undefined ? {} : { revision: request.revision }),
308
491
  });
309
492
  },
493
+ async binary(_authorization, request) {
494
+ if (request.kind === 'path') {
495
+ const asked = pathGrant(request);
496
+ if (asked === null)
497
+ return { kind: 'room-not-readable', locator: request.locator };
498
+ return downloaded(await asked, request.locator);
499
+ }
500
+ const referrer = houseReferrer(request.referrer, tracked);
501
+ if (referrer === null) {
502
+ return { kind: 'rejected', code: 'referrer_unknown', fields: {}, retryable: false };
503
+ }
504
+ const answer = await transport.door(DOOR_FILES_GET, {
505
+ file: request.fileRef,
506
+ referrer: referrer.referrer,
507
+ });
508
+ return downloaded(answer, referrer.locator);
509
+ },
510
+ async link(_authorization, request) {
511
+ const asked = pathGrant(request);
512
+ if (asked === null)
513
+ return { kind: 'room-not-readable', locator: request.locator };
514
+ return granted(await asked, request.locator);
515
+ },
516
+ referrerScope(referrer) {
517
+ const held = houseReferrer(referrer, tracked);
518
+ if (held === null)
519
+ return null;
520
+ if (held.locator === '')
521
+ return { kind: 'conversations' };
522
+ const { path } = held.referrer;
523
+ return { kind: 'room', locator: held.locator, ...(path === undefined ? {} : { path }) };
524
+ },
310
525
  reached(locator, version, target) {
311
526
  return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
312
527
  },
@@ -316,7 +531,16 @@ export function createAgentsHouseAdapter(options) {
316
531
  async action(_authorization, request) {
317
532
  return withAt(await houseAction(transport, request));
318
533
  },
319
- roomStream: { hold: held, reopen: notifications.reopen, observe: notifications.observe },
534
+ roomStream: {
535
+ hold: held,
536
+ reopen: notifications.reopen,
537
+ observe: notifications.observe,
538
+ published(locator, roomVersion) {
539
+ const at = logPosition(roomVersion);
540
+ if (at !== null)
541
+ notifications.published(locator, at);
542
+ },
543
+ },
320
544
  conversations: notifications.conversations,
321
545
  serviceMode: notifications.serviceMode,
322
546
  };
@@ -1,4 +1,4 @@
1
- import type { Capability, DiscoveredRoom, Discovery, ProviderLimit, Retention } from '@agentshouse/mdruntime';
1
+ import type { Capability, DiscoveredRoom, Discovery, Member, ProviderLimit, Retention } from '@agentshouse/mdruntime';
2
2
  import { type LogPosition } from './version.js';
3
3
  export declare const PROVIDER_ACTIONS = "provider-actions";
4
4
  export declare const DISCOVER_PREFIXES: readonly string[];
@@ -13,6 +13,7 @@ export type HouseBounds = {
13
13
  max_inline_body_bytes: number;
14
14
  max_upload_bytes: number;
15
15
  max_prefixes: number;
16
+ max_held_files: number;
16
17
  change_retention_seconds: number;
17
18
  operation_retention_seconds: number;
18
19
  };
@@ -37,6 +38,7 @@ export type HouseAuthority = LogPosition & {
37
38
  operations: HouseOperations;
38
39
  };
39
40
  export type HouseDiscoveryDocument = {
41
+ member: Member;
40
42
  bounds: HouseBounds;
41
43
  authorities: readonly HouseAuthority[];
42
44
  actions: readonly string[];
@@ -64,7 +66,7 @@ export declare function siteTracked(document: HouseSiteDiscovery): TrackedAuthor
64
66
  export declare function trackedFrom(document: HouseDiscoveryDocument): TrackedAuthority[];
65
67
  export declare function providerLimits(bounds: HouseBounds): readonly ProviderLimit[];
66
68
  export declare function declaredRetention(bounds: HouseBounds): Retention;
67
- export declare function capabilities(document: HouseDiscoveryDocument): readonly Capability[];
69
+ export declare function capabilities(actions: HouseDiscoveryDocument['actions'], rooms: readonly Pick<TrackedAuthority, 'authority' | 'houseModel'>[]): readonly Capability[];
68
70
  export declare function siteCapabilities(): readonly Capability[];
69
71
  export declare function discoveredRoom(authority: HouseAuthority): DiscoveredRoom;
70
72
  export declare function mapDiscovery(document: Pick<HouseDiscoveryDocument, 'bounds'>, rooms: readonly DiscoveredRoom[], declared: readonly Capability[]): Discovery;
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,cAAc,EACd,SAAS,EACT,aAAa,EACb,SAAS,EACV,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3E,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAEnD,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAAU,CAAC;AAE1D,MAAM,MAAM,WAAW,GAAG;IACxB,uBAAuB,EAAE,MAAM,CAAC;IAChC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,wBAAwB,EAAE,MAAM,CAAC;IACjC,2BAA2B,EAAE,MAAM,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,kBAAkB,EAAE,UAAU,GAAG,aAAa,CAAC;IAC/C,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,UAAU,EAAE,eAAe,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,SAAS,cAAc,EAAE,CAAC;IACvC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAC5C,CAAC;AAuFF,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,sBAAsB,GAAG,IAAI,CAoB3E;AAmBD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB,GAAG,IAAI,CAa3E;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,kBAAkB,GAAG,gBAAgB,EAAE,CAW5E;AAQD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,sBAAsB,GAAG,gBAAgB,EAAE,CAWhF;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,aAAa,EAAE,CAU5E;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAMhE;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,sBAAsB,GAAG,SAAS,UAAU,EAAE,CAWpF;AAED,wBAAgB,gBAAgB,IAAI,SAAS,UAAU,EAAE,CAIxD;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,cAAc,GAAG,cAAc,CAmBxE;AAED,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,QAAQ,CAAC,EAChD,KAAK,EAAE,SAAS,cAAc,EAAE,EAChC,QAAQ,EAAE,SAAS,UAAU,EAAE,GAC9B,SAAS,CAQX"}
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"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"}