@agentshouse/mdruntime-adapter 0.1.0-cca5d676bc88e → 0.1.0-ccd2ccb1f7214

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 +278 -210
  2. package/dist/actions.d.ts +63 -2
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +157 -138
  5. package/dist/adapter.d.ts +1 -0
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/adapter.js +322 -100
  8. package/dist/discovery.d.ts +4 -4
  9. package/dist/discovery.d.ts.map +1 -1
  10. package/dist/discovery.js +26 -12
  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 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/model.d.ts +1 -1
  21. package/dist/model.d.ts.map +1 -1
  22. package/dist/model.js +21 -7
  23. package/dist/read.d.ts +35 -7
  24. package/dist/read.d.ts.map +1 -1
  25. package/dist/read.js +160 -79
  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 +5 -9
  30. package/dist/refusal.d.ts.map +1 -1
  31. package/dist/refusal.js +31 -17
  32. package/dist/site.d.ts +1 -1
  33. package/dist/site.d.ts.map +1 -1
  34. package/dist/site.js +189 -54
  35. package/dist/stream.d.ts +4 -2
  36. package/dist/stream.d.ts.map +1 -1
  37. package/dist/stream.js +330 -16
  38. package/dist/transport.d.ts +64 -11
  39. package/dist/transport.d.ts.map +1 -1
  40. package/dist/transport.js +3 -1
  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 +97 -20
  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
- import { classifyDoor, discoveryFailure, doorRefusal } from './refusal.js';
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, PRIVATE_LOCATOR, } 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);
@@ -104,95 +171,120 @@ export function createAgentsHouseAdapter(options) {
104
171
  const { authority, position, log_epoch: logEpoch } = body;
105
172
  if (typeof position !== 'string' || typeof logEpoch !== 'string')
106
173
  return result;
107
- const locator = authority === undefined
108
- ? PRIVATE_LOCATOR
109
- : [...tracked.values()].find((entry) => entry.authority === authority)?.locator;
174
+ const locator = [...tracked.values()].find((entry) => entry.authority === authority)?.locator;
110
175
  if (locator === undefined)
111
176
  return result;
112
177
  return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
113
178
  }
114
- async function bundled(body) {
115
- const grant = parseGrant(body);
116
- if (grant === null)
117
- return body;
118
- if (grant.method !== 'GET')
119
- return null;
120
- const answer = await transport.transfer({
121
- act: 'local_copy_bundle',
122
- method: 'GET',
123
- url: grant.url,
124
- answer: 'json',
125
- });
126
- return answer.status === 200 ? answer.body : null;
127
- }
128
179
  function weight(entry) {
129
180
  return contentPrefixes(entry).length;
130
181
  }
131
- async function bootstrapped(entries) {
132
- const answers = [];
133
- for (const part of selections(entries, weight, declared().max_prefixes)) {
134
- const answer = await transport.door(DOOR_BOOTSTRAP, {
135
- prefixes: part.flatMap((entry) => contentPrefixes(entry)),
136
- });
137
- const classified = part.map((entry) => classifyDoor(answer.status, answer.body, entry.locator));
138
- const envelope = classified[0] === null ? parseEnvelope(await bundled(answer.body)) : null;
139
- for (const [index, entry] of part.entries()) {
140
- const failure = classified[index];
141
- if (failure === null || failure === undefined) {
142
- answers.push(envelope === null ? UNCERTAIN_READ : await bootstrappedRoom(envelope, entry));
143
- 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));
144
214
  }
145
- answers.push(failure.kind === 'beyond-horizon' ? UNCERTAIN_READ : failure);
146
215
  }
216
+ open = unreached;
147
217
  }
148
- 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);
149
240
  }
150
241
  function layered(asks) {
151
242
  const depths = new Map();
152
243
  const layers = [];
153
244
  for (const ask of asks) {
154
- const depth = depths.get(ask.entry.authority) ?? 0;
155
- 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);
156
247
  (layers[depth] ??= []).push(ask);
157
248
  }
158
249
  return layers;
159
250
  }
160
251
  async function enumerated(asks) {
161
- const answers = new Map();
162
- 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));
163
254
  for (const part of parts) {
164
255
  let open = [...part];
165
256
  while (open.length > 0) {
166
257
  const answer = await transport.door(DOOR_ENUMERATE, {
167
- prefixes: open.flatMap((ask) => contentPrefixes(ask.entry)),
168
- 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)),
169
260
  });
170
- const classified = open.map((ask) => classifyDoor(answer.status, answer.body, ask.entry.locator));
261
+ const classified = open.map(({ ask }) => classifyRead(answer.status, answer.body, ask.entry.locator));
171
262
  if (classified[0]?.kind === 'beyond-horizon') {
172
263
  const named = doorRefusal(answer.body)?.fields.authority;
173
- const expired = open.filter((ask) => ask.entry.authority === named);
264
+ const expired = open.filter(({ ask }) => ask.entry.authority === named);
174
265
  const answered = expired.length === 0 ? UNCERTAIN_READ : { kind: 'beyond-horizon' };
175
266
  const gone = expired.length === 0 ? open : expired;
176
267
  for (const ask of gone)
177
- answers.set(ask, answered);
268
+ answers[ask.index] = answered;
178
269
  open = open.filter((ask) => !gone.includes(ask));
179
270
  continue;
180
271
  }
181
272
  const enumeration = classified[0] === null ? parseEnumeration(answer.body, 'credential') : null;
182
- for (const [index, ask] of open.entries()) {
183
- const failure = classified[index];
273
+ for (const [answerIndex, { ask, index }] of open.entries()) {
274
+ const failure = classified[answerIndex];
184
275
  if (failure !== null && failure !== undefined) {
185
- answers.set(ask, failure);
276
+ answers[index] = failure;
186
277
  continue;
187
278
  }
188
- answers.set(ask, enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask));
279
+ answers[index] =
280
+ enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask);
189
281
  }
190
282
  open = [];
191
283
  }
192
284
  }
193
- return asks.map((ask) => answers.get(ask) ?? UNCERTAIN_READ);
285
+ return answers;
194
286
  }
195
- const bootstrapTogether = gathered(bootstrapped);
287
+ const bootstrapTogether = gatheredEach(bootstrapped);
196
288
  const enumerateTogether = gathered(enumerated);
197
289
  async function uploaded(head, changes, delta, bytes, entry, handed, keep) {
198
290
  const paths = batchPaths(changes);
@@ -209,9 +301,7 @@ export function createAgentsHouseAdapter(options) {
209
301
  return { outcome: UNCERTAIN };
210
302
  const delivered = await transport.transfer({
211
303
  act: 'authored_batch_upload',
212
- method: 'POST',
213
304
  url: grant.url,
214
- answer: 'json',
215
305
  body: `${grant.operation}\n${delta}`,
216
306
  });
217
307
  if (delivered.status !== 200)
@@ -223,7 +313,30 @@ export function createAgentsHouseAdapter(options) {
223
313
  await keep(upload);
224
314
  return { body: { ...head, paths, upload, runtime: notifications.runtime } };
225
315
  }
226
- 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) {
227
340
  const inline = { ...head, changes, runtime: notifications.runtime };
228
341
  const prepared = uploadable(inline, bytes, declared())
229
342
  ? await uploaded(head, changes, delta, bytes, entry, handed, keep)
@@ -231,38 +344,105 @@ export function createAgentsHouseAdapter(options) {
231
344
  if ('outcome' in prepared)
232
345
  return prepared.outcome;
233
346
  const answer = await transport.door(DOOR_BATCH, prepared.body);
234
- 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
+ });
235
392
  }
236
393
  return {
237
394
  async authorize() {
238
395
  return options.authorize();
239
396
  },
240
- async discover(_authorization) {
241
- const answer = await transport.door(DOOR_DISCOVER, { prefixes: [...DISCOVER_PREFIXES] });
242
- const classified = classifyDoor(answer.status, answer.body, PRIVATE_LOCATOR);
243
- if (classified !== null)
244
- return discoveryFailure(classified);
397
+ async discover(_authorization, scope) {
398
+ const answer = await transport.door(DOOR_DISCOVER, {
399
+ prefixes: [...DISCOVER_PREFIXES],
400
+ ...(scope === undefined ? {} : { rooms: scope.authorities }),
401
+ });
402
+ const failed = discoveryFailure(answer.status, answer.body);
403
+ if (failed !== null)
404
+ return failed;
245
405
  const document = parseDiscovery(answer.body);
246
406
  if (document === null)
247
407
  return { kind: 'transport-uncertainty' };
248
408
  bounds = document.bounds;
249
409
  const entries = trackedFrom(document);
250
- 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);
251
425
  for (const entry of entries)
252
426
  tracked.set(entry.locator, entry);
253
427
  for (const locator of handed.keys())
254
428
  if (!tracked.has(locator))
255
429
  handed.delete(locator);
256
- notifications.track(entries);
257
- return mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions));
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
+ };
258
436
  },
259
437
  async bootstrap(_authorization, request) {
260
438
  const entry = authorityFor(request.locator);
261
439
  if (entry === undefined)
262
440
  return { kind: 'room-not-readable', locator: request.locator };
263
- if (request.pageToken !== undefined)
264
- return { kind: 'transport-uncertainty' };
265
- 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);
266
446
  },
267
447
  async refresh(_authorization, request) {
268
448
  const entry = authorityFor(request.locator);
@@ -289,10 +469,11 @@ export function createAgentsHouseAdapter(options) {
289
469
  const delta = deltaBody(mapped.changes);
290
470
  const bytes = deltaBytes(delta);
291
471
  const handed = request.upload;
292
- 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);
293
474
  if (handed === undefined || !lapsedGrant(outcome))
294
475
  return adopted(entry, outcome);
295
- 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));
296
477
  },
297
478
  async history(_authorization, request) {
298
479
  const entry = authorityFor(request.locator);
@@ -309,6 +490,38 @@ export function createAgentsHouseAdapter(options) {
309
490
  ...(request.revision === undefined ? {} : { revision: request.revision }),
310
491
  });
311
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
+ },
312
525
  reached(locator, version, target) {
313
526
  return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
314
527
  },
@@ -318,7 +531,16 @@ export function createAgentsHouseAdapter(options) {
318
531
  async action(_authorization, request) {
319
532
  return withAt(await houseAction(transport, request));
320
533
  },
321
- roomStream: { hold: held, 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
+ },
322
544
  conversations: notifications.conversations,
323
545
  serviceMode: notifications.serviceMode,
324
546
  };
@@ -1,8 +1,7 @@
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[];
5
- export declare const PRIVATE_HANDLE = "private";
6
5
  export type HouseBounds = {
7
6
  max_authored_file_bytes: number;
8
7
  max_batch_operations: number;
@@ -14,6 +13,7 @@ export type HouseBounds = {
14
13
  max_inline_body_bytes: number;
15
14
  max_upload_bytes: number;
16
15
  max_prefixes: number;
16
+ max_held_files: number;
17
17
  change_retention_seconds: number;
18
18
  operation_retention_seconds: number;
19
19
  };
@@ -38,6 +38,7 @@ export type HouseAuthority = LogPosition & {
38
38
  operations: HouseOperations;
39
39
  };
40
40
  export type HouseDiscoveryDocument = {
41
+ member: Member;
41
42
  bounds: HouseBounds;
42
43
  authorities: readonly HouseAuthority[];
43
44
  actions: readonly string[];
@@ -62,11 +63,10 @@ export type HouseSiteDiscovery = {
62
63
  export declare function parseDiscovery(body: unknown): HouseDiscoveryDocument | null;
63
64
  export declare function parseSiteDiscovery(body: unknown): HouseSiteDiscovery | null;
64
65
  export declare function siteTracked(document: HouseSiteDiscovery): TrackedAuthority[];
65
- export declare function locatorOf(authority: HouseAuthority): string;
66
66
  export declare function trackedFrom(document: HouseDiscoveryDocument): TrackedAuthority[];
67
67
  export declare function providerLimits(bounds: HouseBounds): readonly ProviderLimit[];
68
68
  export declare function declaredRetention(bounds: HouseBounds): Retention;
69
- export declare function capabilities(actions: readonly string[]): readonly Capability[];
69
+ export declare function capabilities(actions: HouseDiscoveryDocument['actions'], rooms: readonly Pick<TrackedAuthority, 'authority' | 'houseModel'>[]): readonly Capability[];
70
70
  export declare function siteCapabilities(): readonly Capability[];
71
71
  export declare function discoveredRoom(authority: HouseAuthority): DiscoveredRoom;
72
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":"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"}
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"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"}