@agentshouse/mdruntime-adapter 0.1.0-cf22c8563f9f4 → 0.1.0-cf9ab9b7cbbca

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 +269 -214
  2. package/dist/actions.d.ts +62 -1
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +153 -133
  5. package/dist/adapter.d.ts +1 -0
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/adapter.js +286 -91
  8. package/dist/discovery.d.ts +3 -1
  9. package/dist/discovery.d.ts.map +1 -1
  10. package/dist/discovery.js +18 -3
  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 +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 +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 +165 -48
  35. package/dist/stream.d.ts +6 -4
  36. package/dist/stream.d.ts.map +1 -1
  37. package/dist/stream.js +308 -16
  38. package/dist/transport.d.ts +54 -11
  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,69 +57,115 @@ 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
  }
66
- function held(rooms) {
137
+ function held(rooms, demand) {
67
138
  const kept = rooms.flatMap((room) => {
68
139
  const entry = tracked.get(room.locator);
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
  });
76
- notifications.send(kept);
147
+ notifications.send(kept, demand === null
148
+ ? null
149
+ : {
150
+ rooms: demand.rooms.flatMap((locator) => {
151
+ const entry = tracked.get(locator);
152
+ return entry === undefined ? [] : [entry.authority];
153
+ }),
154
+ conversations: demand.conversations,
155
+ });
77
156
  }
78
157
  function adopted(entry, outcome) {
79
158
  if (outcome.kind !== 'accepted')
80
159
  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);
160
+ const held = handed.get(entry.locator);
161
+ if (held === undefined)
162
+ return outcome;
163
+ const follows = followsVersion(held.roomVersion, outcome.roomVersion);
164
+ handed.set(entry.locator, acceptedHanded(held, outcome, follows ? outcome.roomVersion : held.roomVersion));
165
+ const at = logPosition(outcome.roomVersion);
89
166
  if (follows && at !== null)
90
167
  notifications.reach(entry.authority, at);
91
- return relinked.attachments === undefined
92
- ? accepted
93
- : { ...accepted, attachments: relinked.attachments };
168
+ return outcome;
94
169
  }
95
170
  function authorityFor(locator) {
96
171
  return tracked.get(locator);
@@ -109,88 +184,115 @@ export function createAgentsHouseAdapter(options) {
109
184
  return result;
110
185
  return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
111
186
  }
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
187
  function weight(entry) {
127
188
  return contentPrefixes(entry).length;
128
189
  }
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;
190
+ function holding(asks, after) {
191
+ const budget = Math.floor(declared().max_held_files / Math.max(1, asks.filter((ask) => ask.held !== undefined).length));
192
+ return asks.flatMap((ask) => heldFiles(ask.entry.authority, ask.held, after, budget) ?? []);
193
+ }
194
+ async function bootstrapped(asks, answer) {
195
+ let open = asks.map((asked, ask) => ({ entry: asked.entry, asked, ask }));
196
+ while (open.length > 0) {
197
+ const unreached = [];
198
+ for (const part of selections(open, ({ entry }) => weight(entry), declared().max_prefixes)) {
199
+ const held = holding(part.map(({ asked }) => asked), null);
200
+ const answered = await transport.door(DOOR_BOOTSTRAP, {
201
+ prefixes: part.flatMap(({ entry }) => contentPrefixes(entry)),
202
+ ...(held.length === 0 ? {} : { held }),
203
+ });
204
+ const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
205
+ const envelope = classified[0] === null ? parseEnvelope(answered.body) : null;
206
+ for (const [index, asked] of part.entries()) {
207
+ const failure = classified[index];
208
+ if (failure !== null && failure !== undefined) {
209
+ answer(asked.ask, failure);
210
+ continue;
211
+ }
212
+ if (envelope === null) {
213
+ answer(asked.ask, UNCERTAIN_READ);
214
+ continue;
215
+ }
216
+ const scope = envelope.scopes.find((candidate) => matchesScope(candidate, asked.entry));
217
+ if (scope === undefined && envelope.next !== undefined) {
218
+ unreached.push(asked);
219
+ continue;
220
+ }
221
+ answer(asked.ask, await bootstrappedRoom(envelope, scope, asked.asked, null));
142
222
  }
143
- answers.push(failure);
144
223
  }
224
+ open = unreached;
145
225
  }
146
- return answers;
226
+ }
227
+ async function continuedBootstrap(asked, continued) {
228
+ const { entry } = asked;
229
+ const after = {
230
+ room: entry.authority,
231
+ path: continued.after,
232
+ ...(continued.cut === undefined ? {} : { cut: continued.cut }),
233
+ };
234
+ const held = holding([asked], continued.after);
235
+ const answered = await transport.door(DOOR_BOOTSTRAP, {
236
+ prefixes: contentPrefixes(entry),
237
+ after,
238
+ ...(held.length === 0 ? {} : { held }),
239
+ });
240
+ const failure = classifyRead(answered.status, answered.body, entry.locator);
241
+ if (failure !== null)
242
+ return failure;
243
+ const envelope = parseEnvelope(answered.body);
244
+ if (envelope === null)
245
+ return UNCERTAIN_READ;
246
+ const scope = envelope.scopes.find((candidate) => matchesScope(candidate, entry));
247
+ return bootstrappedRoom(envelope, scope, asked, continued);
147
248
  }
148
249
  function layered(asks) {
149
250
  const depths = new Map();
150
251
  const layers = [];
151
252
  for (const ask of asks) {
152
- const depth = depths.get(ask.entry.authority) ?? 0;
153
- depths.set(ask.entry.authority, depth + 1);
253
+ const depth = depths.get(ask.ask.entry.authority) ?? 0;
254
+ depths.set(ask.ask.entry.authority, depth + 1);
154
255
  (layers[depth] ??= []).push(ask);
155
256
  }
156
257
  return layers;
157
258
  }
158
259
  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));
260
+ const answers = [];
261
+ const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, ({ ask }) => weight(ask.entry), declared().max_prefixes));
161
262
  for (const part of parts) {
162
263
  let open = [...part];
163
264
  while (open.length > 0) {
164
265
  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)),
266
+ prefixes: open.flatMap(({ ask }) => contentPrefixes(ask.entry)),
267
+ positions: open.map(({ ask }) => presented(ask.entry.authority, ask.from)),
167
268
  });
168
- const classified = open.map((ask) => classifyRead(answer.status, answer.body, ask.entry.locator));
269
+ const classified = open.map(({ ask }) => classifyRead(answer.status, answer.body, ask.entry.locator));
169
270
  if (classified[0]?.kind === 'beyond-horizon') {
170
271
  const named = doorRefusal(answer.body)?.fields.authority;
171
- const expired = open.filter((ask) => ask.entry.authority === named);
272
+ const expired = open.filter(({ ask }) => ask.entry.authority === named);
172
273
  const answered = expired.length === 0 ? UNCERTAIN_READ : { kind: 'beyond-horizon' };
173
274
  const gone = expired.length === 0 ? open : expired;
174
275
  for (const ask of gone)
175
- answers.set(ask, answered);
276
+ answers[ask.index] = answered;
176
277
  open = open.filter((ask) => !gone.includes(ask));
177
278
  continue;
178
279
  }
179
280
  const enumeration = classified[0] === null ? parseEnumeration(answer.body, 'credential') : null;
180
- for (const [index, ask] of open.entries()) {
181
- const failure = classified[index];
281
+ for (const [answerIndex, { ask, index }] of open.entries()) {
282
+ const failure = classified[answerIndex];
182
283
  if (failure !== null && failure !== undefined) {
183
- answers.set(ask, failure);
284
+ answers[index] = failure;
184
285
  continue;
185
286
  }
186
- answers.set(ask, enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask));
287
+ answers[index] =
288
+ enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask);
187
289
  }
188
290
  open = [];
189
291
  }
190
292
  }
191
- return asks.map((ask) => answers.get(ask) ?? UNCERTAIN_READ);
293
+ return answers;
192
294
  }
193
- const bootstrapTogether = gathered(bootstrapped);
295
+ const bootstrapTogether = gatheredEach(bootstrapped);
194
296
  const enumerateTogether = gathered(enumerated);
195
297
  async function uploaded(head, changes, delta, bytes, entry, handed, keep) {
196
298
  const paths = batchPaths(changes);
@@ -207,9 +309,7 @@ export function createAgentsHouseAdapter(options) {
207
309
  return { outcome: UNCERTAIN };
208
310
  const delivered = await transport.transfer({
209
311
  act: 'authored_batch_upload',
210
- method: 'POST',
211
312
  url: grant.url,
212
- answer: 'json',
213
313
  body: `${grant.operation}\n${delta}`,
214
314
  });
215
315
  if (delivered.status !== 200)
@@ -221,7 +321,30 @@ export function createAgentsHouseAdapter(options) {
221
321
  await keep(upload);
222
322
  return { body: { ...head, paths, upload, runtime: notifications.runtime } };
223
323
  }
224
- async function submit(head, changes, delta, bytes, entry, handed, keep) {
324
+ async function staged(answer, batch) {
325
+ const refusal = doorRefusal(answer.body);
326
+ if (refusal?.code !== 'files_unstaged')
327
+ return null;
328
+ const grants = refusal.fields.upload;
329
+ if (!Array.isArray(grants))
330
+ return false;
331
+ for (const grant of grants) {
332
+ const { url, operation, sha256 } = grant;
333
+ const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === sha256);
334
+ if (typeof url !== 'string' || typeof operation !== 'string' || change === undefined)
335
+ return false;
336
+ const delivered = await transport.transfer({
337
+ act: 'binary_stage',
338
+ url,
339
+ body: change.blob,
340
+ operation,
341
+ });
342
+ if (delivered.status !== 200)
343
+ return false;
344
+ }
345
+ return true;
346
+ }
347
+ async function submit(batch, head, changes, delta, bytes, entry, handed, keep) {
225
348
  const inline = { ...head, changes, runtime: notifications.runtime };
226
349
  const prepared = uploadable(inline, bytes, declared())
227
350
  ? await uploaded(head, changes, delta, bytes, entry, handed, keep)
@@ -229,7 +352,33 @@ export function createAgentsHouseAdapter(options) {
229
352
  if ('outcome' in prepared)
230
353
  return prepared.outcome;
231
354
  const answer = await transport.door(DOOR_BATCH, prepared.body);
232
- return mutationOutcome(answer.status, answer.body, entry.locator, entry.handle);
355
+ const stagedFiles = await staged(answer, batch);
356
+ if (stagedFiles === false)
357
+ return UNCERTAIN;
358
+ const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
359
+ if (stagedFiles === true && (await staged(settled, batch)) !== null)
360
+ return UNCERTAIN;
361
+ return mutationOutcome(settled.status, settled.body, entry.locator, entry.handle);
362
+ }
363
+ async function downloaded(answer, locator) {
364
+ const failure = classifyDoor(answer.status, answer.body, locator);
365
+ if (failure !== null) {
366
+ return failure.kind === 'scoped-refusal'
367
+ ? {
368
+ kind: 'rejected',
369
+ code: failure.code,
370
+ fields: failure.fields,
371
+ retryable: failure.retryable,
372
+ }
373
+ : failure;
374
+ }
375
+ const grant = parseGrant(answer.body);
376
+ if (grant === null || grant.method !== 'GET')
377
+ return UNCERTAIN_READ;
378
+ const read = await transport.download(grant.url);
379
+ if (read.status !== 200 || read.blob === null)
380
+ return UNCERTAIN_READ;
381
+ return { kind: 'binary', blob: read.blob };
233
382
  }
234
383
  return {
235
384
  async authorize() {
@@ -252,15 +401,20 @@ export function createAgentsHouseAdapter(options) {
252
401
  if (!tracked.has(locator))
253
402
  handed.delete(locator);
254
403
  notifications.track(entries);
255
- return mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document));
404
+ return {
405
+ ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document)),
406
+ member: document.member,
407
+ };
256
408
  },
257
409
  async bootstrap(_authorization, request) {
258
410
  const entry = authorityFor(request.locator);
259
411
  if (entry === undefined)
260
412
  return { kind: 'room-not-readable', locator: request.locator };
261
- if (request.pageToken !== undefined)
262
- return { kind: 'transport-uncertainty' };
263
- return bootstrapTogether(entry);
413
+ const asked = { entry, held: request.held };
414
+ if (request.pageToken !== undefined) {
415
+ return continuedBootstrap(asked, JSON.parse(request.pageToken));
416
+ }
417
+ return bootstrapTogether(asked);
264
418
  },
265
419
  async refresh(_authorization, request) {
266
420
  const entry = authorityFor(request.locator);
@@ -287,10 +441,11 @@ export function createAgentsHouseAdapter(options) {
287
441
  const delta = deltaBody(mapped.changes);
288
442
  const bytes = deltaBytes(delta);
289
443
  const handed = request.upload;
290
- const outcome = await submit(head, mapped.changes, delta, bytes, entry, handed, keep);
444
+ const batch = request.changeBatch;
445
+ const outcome = await submit(batch, head, mapped.changes, delta, bytes, entry, handed, keep);
291
446
  if (handed === undefined || !lapsedGrant(outcome))
292
447
  return adopted(entry, outcome);
293
- return adopted(entry, await submit(head, mapped.changes, delta, bytes, entry, undefined, keep));
448
+ return adopted(entry, await submit(batch, head, mapped.changes, delta, bytes, entry, undefined, keep));
294
449
  },
295
450
  async history(_authorization, request) {
296
451
  const entry = authorityFor(request.locator);
@@ -307,6 +462,37 @@ export function createAgentsHouseAdapter(options) {
307
462
  ...(request.revision === undefined ? {} : { revision: request.revision }),
308
463
  });
309
464
  },
465
+ async binary(_authorization, request) {
466
+ if (request.kind === 'path') {
467
+ const entry = authorityFor(request.locator);
468
+ if (entry === undefined)
469
+ return { kind: 'room-not-readable', locator: request.locator };
470
+ const answer = await transport.door(DOOR_FILES_GET, {
471
+ room_ref: entry.authority,
472
+ path: request.path,
473
+ revision: request.revision,
474
+ });
475
+ return downloaded(answer, request.locator);
476
+ }
477
+ const referrer = houseReferrer(request.referrer, tracked);
478
+ if (referrer === null) {
479
+ return { kind: 'rejected', code: 'referrer_unknown', fields: {}, retryable: false };
480
+ }
481
+ const answer = await transport.door(DOOR_FILES_GET, {
482
+ file: request.fileRef,
483
+ referrer: referrer.referrer,
484
+ });
485
+ return downloaded(answer, referrer.locator);
486
+ },
487
+ referrerScope(referrer) {
488
+ const held = houseReferrer(referrer, tracked);
489
+ if (held === null)
490
+ return null;
491
+ if (held.locator === '')
492
+ return { kind: 'conversations' };
493
+ const { path } = held.referrer;
494
+ return { kind: 'room', locator: held.locator, ...(path === undefined ? {} : { path }) };
495
+ },
310
496
  reached(locator, version, target) {
311
497
  return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
312
498
  },
@@ -316,7 +502,16 @@ export function createAgentsHouseAdapter(options) {
316
502
  async action(_authorization, request) {
317
503
  return withAt(await houseAction(transport, request));
318
504
  },
319
- roomStream: { hold: held, observe: notifications.observe },
505
+ roomStream: {
506
+ hold: held,
507
+ reopen: notifications.reopen,
508
+ observe: notifications.observe,
509
+ published(locator, roomVersion) {
510
+ const at = logPosition(roomVersion);
511
+ if (at !== null)
512
+ notifications.published(locator, at);
513
+ },
514
+ },
320
515
  conversations: notifications.conversations,
321
516
  serviceMode: notifications.serviceMode,
322
517
  };
@@ -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[];
@@ -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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"}
package/dist/discovery.js CHANGED
@@ -1,4 +1,4 @@
1
- import { declaredActions, houseConversationAgents } from './actions.js';
1
+ import { declaredActions, houseConversationAgents, SITE_SUBMIT } from './actions.js';
2
2
  import { readPosition, roomVersion } from './version.js';
3
3
  export const PROVIDER_ACTIONS = 'provider-actions';
4
4
  export const DISCOVER_PREFIXES = ['/'];
@@ -13,6 +13,7 @@ const BOUND_KEYS = [
13
13
  'max_inline_body_bytes',
14
14
  'max_upload_bytes',
15
15
  'max_prefixes',
16
+ 'max_held_files',
16
17
  'change_retention_seconds',
17
18
  'operation_retention_seconds',
18
19
  ];
@@ -46,6 +47,14 @@ function parseHouseModel(value) {
46
47
  return undefined;
47
48
  return { root: held.root, paths };
48
49
  }
50
+ function parseMember(value) {
51
+ if (typeof value !== 'object' || value === null)
52
+ return null;
53
+ const { user, name } = value;
54
+ if (typeof user !== 'string' || typeof name !== 'string')
55
+ return null;
56
+ return { kind: 'user', id: user, name };
57
+ }
49
58
  function parseOperations(value) {
50
59
  if (typeof value !== 'object' || value === null)
51
60
  return null;
@@ -98,6 +107,9 @@ export function parseDiscovery(body) {
98
107
  if (typeof body !== 'object' || body === null)
99
108
  return null;
100
109
  const held = body;
110
+ const member = parseMember(held.member);
111
+ if (member === null)
112
+ return null;
101
113
  const bounds = parseBounds(held.bounds);
102
114
  if (bounds === null)
103
115
  return null;
@@ -115,7 +127,7 @@ export function parseDiscovery(body) {
115
127
  return null;
116
128
  authorities.push(authority);
117
129
  }
118
- return { bounds, authorities, actions };
130
+ return { member, bounds, authorities, actions };
119
131
  }
120
132
  function parseSiteAuthority(value) {
121
133
  if (typeof value !== 'object' || value === null)
@@ -218,7 +230,10 @@ export function capabilities(document) {
218
230
  }
219
231
  export function siteCapabilities() {
220
232
  return [
221
- { name: PROVIDER_ACTIONS, actions: [{ name: 'submit', inputShape: {}, resultKind: 'house' }] },
233
+ {
234
+ name: PROVIDER_ACTIONS,
235
+ actions: [{ name: SITE_SUBMIT, inputShape: {}, resultKind: 'house' }],
236
+ },
222
237
  ];
223
238
  }
224
239
  export function discoveredRoom(authority) {
package/dist/gather.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export declare function gatheredEach<A, R>(send: (asks: readonly A[], answer: (ask: number, result: R) => void) => Promise<void>): (ask: A) => Promise<R>;
1
2
  export declare function gathered<A, R>(send: (asks: readonly A[]) => Promise<readonly R[]>): (ask: A) => Promise<R>;
2
3
  export declare function selections<T>(items: readonly T[], weight: (item: T) => number, limit: number): readonly (readonly T[])[];
3
4
  //# sourceMappingURL=gather.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"gather.d.ts","sourceRoot":"","sources":["../src/gather.ts"],"names":[],"mappings":"AAMA,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAC3B,IAAI,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,KAAK,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,GAClD,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CA0BxB;AAED,wBAAgB,UAAU,CAAC,CAAC,EAC1B,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,EAC3B,KAAK,EAAE,MAAM,GACZ,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAgB3B"}
1
+ {"version":3,"file":"gather.d.ts","sourceRoot":"","sources":["../src/gather.ts"],"names":[],"mappings":"AAMA,wBAAgB,YAAY,CAAC,CAAC,EAAE,CAAC,EAC/B,IAAI,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,KAAK,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,GACpF,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAwBxB;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAC3B,IAAI,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,KAAK,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,GAClD,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAMxB;AAED,wBAAgB,UAAU,CAAC,CAAC,EAC1B,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,EAC3B,KAAK,EAAE,MAAM,GACZ,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAgB3B"}