@agentshouse/mdruntime-adapter 0.1.0-cd59fd8945dcb → 0.1.0-cda5b3be3317e

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 (46) hide show
  1. package/AGENTS.md +271 -262
  2. package/dist/actions.d.ts +23 -0
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +68 -31
  5. package/dist/adapter.d.ts +1 -0
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/adapter.js +261 -64
  8. package/dist/discovery.d.ts +2 -1
  9. package/dist/discovery.d.ts.map +1 -1
  10. package/dist/discovery.js +10 -6
  11. package/dist/handed.d.ts +15 -0
  12. package/dist/handed.d.ts.map +1 -0
  13. package/dist/handed.js +38 -0
  14. package/dist/index.d.ts +2 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +1 -1
  17. package/dist/model.d.ts +0 -1
  18. package/dist/model.d.ts.map +1 -1
  19. package/dist/model.js +0 -3
  20. package/dist/read.d.ts +29 -6
  21. package/dist/read.d.ts.map +1 -1
  22. package/dist/read.js +150 -52
  23. package/dist/referrer.d.ts +7 -0
  24. package/dist/referrer.d.ts.map +1 -0
  25. package/dist/referrer.js +35 -0
  26. package/dist/refusal.d.ts.map +1 -1
  27. package/dist/refusal.js +2 -1
  28. package/dist/site.d.ts +1 -1
  29. package/dist/site.d.ts.map +1 -1
  30. package/dist/site.js +174 -47
  31. package/dist/stream.d.ts +3 -2
  32. package/dist/stream.d.ts.map +1 -1
  33. package/dist/stream.js +325 -16
  34. package/dist/transport.d.ts +62 -6
  35. package/dist/transport.d.ts.map +1 -1
  36. package/dist/transport.js +3 -0
  37. package/dist/version.d.ts +1 -0
  38. package/dist/version.d.ts.map +1 -1
  39. package/dist/version.js +1 -1
  40. package/dist/write.d.ts +2 -9
  41. package/dist/write.d.ts.map +1 -1
  42. package/dist/write.js +61 -8
  43. package/package.json +2 -3
  44. package/dist/links.d.ts +0 -20
  45. package/dist/links.d.ts.map +0 -1
  46. package/dist/links.js +0 -104
package/dist/adapter.js CHANGED
@@ -2,22 +2,51 @@ import { DISCOVER_PREFIXES, capabilities, discoveredRoom, mapDiscovery, parseDis
2
2
  import { partModels, streamedModels, viewModels } from './model.js';
3
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,12 +57,22 @@ 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, scope, entry, continued) {
32
- if (scope === undefined)
60
+ async function bootstrappedRoom(envelope, sent, asked, continued) {
61
+ const { entry } = asked;
62
+ if (sent === undefined)
33
63
  return { kind: 'room-not-readable', locator: entry.locator };
34
- const next = envelope.next?.room === entry.authority ? envelope.next.path : null;
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;
35
74
  if (continued === null && next === null)
36
- handed.set(entry.locator, scopeLinks(scope));
75
+ handed.set(entry.locator, scopeHanded(scope));
37
76
  else
38
77
  handed.delete(entry.locator);
39
78
  notifications.reach(entry.authority, scope);
@@ -42,36 +81,56 @@ export function createAgentsHouseAdapter(options) {
42
81
  ...entry.schemas.filter((schema) => scope.files.some((file) => file.path === schema)),
43
82
  ];
44
83
  const models = await partModels(scope, entry, new Set(seen), next === null);
45
- const continuation = next === null ? null : { after: next, seen };
84
+ const continuation = next === null
85
+ ? null
86
+ : { after: next, seen, ...(cursor?.cut === undefined ? {} : { cut: cursor.cut }) };
46
87
  return {
47
88
  ...bootstrapOf(scope),
48
89
  ...(models === undefined ? {} : { models }),
49
90
  ...(continuation === null ? {} : { pageToken: JSON.stringify(continuation) }),
50
91
  };
51
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 });
110
+ }
52
111
  async function enumeratedRoom(enumeration, ask) {
53
- const view = viewOf(enumeration, ask.entry);
54
- if (view === undefined)
112
+ const first = viewOf(enumeration, ask.entry);
113
+ if (first === undefined)
55
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;
56
118
  notifications.reach(ask.entry.authority, view);
57
119
  const answer = refreshFrom(view, ask.fromRoomVersion);
58
120
  if (answer.kind !== 'net-advancement')
59
121
  return answer;
60
- 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)));
61
123
  const models = await viewModels(view, ask.entry);
62
124
  return models === undefined ? answer : { ...answer, models };
63
125
  }
64
126
  async function streamed(entry, request, from) {
65
- const links = handed.get(entry.locator);
66
- if (links?.roomVersion !== request.fromRoomVersion)
127
+ const held = handed.get(entry.locator);
128
+ if (held?.roomVersion !== request.fromRoomVersion)
67
129
  return null;
68
- 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);
69
131
  if (advanced === null)
70
132
  return null;
71
- const relinked = streamedLinks(links, advanced);
72
- if (relinked === null)
73
- return null;
74
- handed.set(entry.locator, relinked);
133
+ handed.set(entry.locator, streamedHanded(held, advanced));
75
134
  const models = await streamedModels(advanced.schemas, entry, 'credential');
76
135
  return models === undefined ? advanced.page : { ...advanced.page, models };
77
136
  }
@@ -81,7 +140,7 @@ export function createAgentsHouseAdapter(options) {
81
140
  const at = logPosition(room.roomVersion);
82
141
  if (entry === undefined || at === null)
83
142
  return [];
84
- handed.set(room.locator, heldLinks(room));
143
+ handed.set(room.locator, heldHanded(room));
85
144
  notifications.reach(entry.authority, at);
86
145
  return [presented(entry.authority, at)];
87
146
  });
@@ -90,19 +149,15 @@ export function createAgentsHouseAdapter(options) {
90
149
  function adopted(entry, outcome) {
91
150
  if (outcome.kind !== 'accepted')
92
151
  return outcome;
93
- const { originals, ...accepted } = outcome;
94
- const links = handed.get(entry.locator);
95
- if (links === undefined)
96
- return accepted;
97
- const follows = followsVersion(links.roomVersion, accepted.roomVersion);
98
- const relinked = acceptedLinks(links, accepted, follows ? accepted.roomVersion : links.roomVersion, originals);
99
- handed.set(entry.locator, relinked.links);
100
- 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);
101
158
  if (follows && at !== null)
102
159
  notifications.reach(entry.authority, at);
103
- return relinked.attachments === undefined
104
- ? accepted
105
- : { ...accepted, attachments: relinked.attachments };
160
+ return outcome;
106
161
  }
107
162
  function authorityFor(locator) {
108
163
  return tracked.get(locator);
@@ -124,13 +179,19 @@ export function createAgentsHouseAdapter(options) {
124
179
  function weight(entry) {
125
180
  return contentPrefixes(entry).length;
126
181
  }
127
- async function bootstrapped(entries, answer) {
128
- let open = entries.map((entry, ask) => ({ entry, ask }));
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 }));
129
188
  while (open.length > 0) {
130
189
  const unreached = [];
131
190
  for (const part of selections(open, ({ entry }) => weight(entry), declared().max_prefixes)) {
191
+ const held = holding(part.map(({ asked }) => asked), null);
132
192
  const answered = await transport.door(DOOR_BOOTSTRAP, {
133
193
  prefixes: part.flatMap(({ entry }) => contentPrefixes(entry)),
194
+ ...(held.length === 0 ? {} : { held }),
134
195
  });
135
196
  const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
136
197
  const envelope = classified[0] === null ? parseEnvelope(answered.body) : null;
@@ -149,71 +210,79 @@ export function createAgentsHouseAdapter(options) {
149
210
  unreached.push(asked);
150
211
  continue;
151
212
  }
152
- answer(asked.ask, await bootstrappedRoom(envelope, scope, asked.entry, null));
213
+ answer(asked.ask, await bootstrappedRoom(envelope, scope, asked.asked, null));
153
214
  }
154
215
  }
155
216
  open = unreached;
156
217
  }
157
218
  }
158
- async function continuedBootstrap(entry, continued) {
159
- const after = { room: entry.authority, path: continued.after };
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);
160
227
  const answered = await transport.door(DOOR_BOOTSTRAP, {
161
228
  prefixes: contentPrefixes(entry),
162
229
  after,
230
+ ...(held.length === 0 ? {} : { held }),
163
231
  });
164
- const failure = classifyDoor(answered.status, answered.body, entry.locator);
232
+ const failure = classifyRead(answered.status, answered.body, entry.locator);
165
233
  if (failure !== null)
166
234
  return failure;
167
235
  const envelope = parseEnvelope(answered.body);
168
236
  if (envelope === null)
169
237
  return UNCERTAIN_READ;
170
238
  const scope = envelope.scopes.find((candidate) => matchesScope(candidate, entry));
171
- return bootstrappedRoom(envelope, scope, entry, continued);
239
+ return bootstrappedRoom(envelope, scope, asked, continued);
172
240
  }
173
241
  function layered(asks) {
174
242
  const depths = new Map();
175
243
  const layers = [];
176
244
  for (const ask of asks) {
177
- const depth = depths.get(ask.entry.authority) ?? 0;
178
- 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);
179
247
  (layers[depth] ??= []).push(ask);
180
248
  }
181
249
  return layers;
182
250
  }
183
251
  async function enumerated(asks) {
184
- const answers = new Map();
185
- 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));
186
254
  for (const part of parts) {
187
255
  let open = [...part];
188
256
  while (open.length > 0) {
189
257
  const answer = await transport.door(DOOR_ENUMERATE, {
190
- prefixes: open.flatMap((ask) => contentPrefixes(ask.entry)),
191
- 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)),
192
260
  });
193
- 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));
194
262
  if (classified[0]?.kind === 'beyond-horizon') {
195
263
  const named = doorRefusal(answer.body)?.fields.authority;
196
- const expired = open.filter((ask) => ask.entry.authority === named);
264
+ const expired = open.filter(({ ask }) => ask.entry.authority === named);
197
265
  const answered = expired.length === 0 ? UNCERTAIN_READ : { kind: 'beyond-horizon' };
198
266
  const gone = expired.length === 0 ? open : expired;
199
267
  for (const ask of gone)
200
- answers.set(ask, answered);
268
+ answers[ask.index] = answered;
201
269
  open = open.filter((ask) => !gone.includes(ask));
202
270
  continue;
203
271
  }
204
272
  const enumeration = classified[0] === null ? parseEnumeration(answer.body, 'credential') : null;
205
- for (const [index, ask] of open.entries()) {
206
- const failure = classified[index];
273
+ for (const [answerIndex, { ask, index }] of open.entries()) {
274
+ const failure = classified[answerIndex];
207
275
  if (failure !== null && failure !== undefined) {
208
- answers.set(ask, failure);
276
+ answers[index] = failure;
209
277
  continue;
210
278
  }
211
- answers.set(ask, enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask));
279
+ answers[index] =
280
+ enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask);
212
281
  }
213
282
  open = [];
214
283
  }
215
284
  }
216
- return asks.map((ask) => answers.get(ask) ?? UNCERTAIN_READ);
285
+ return answers;
217
286
  }
218
287
  const bootstrapTogether = gatheredEach(bootstrapped);
219
288
  const enumerateTogether = gathered(enumerated);
@@ -244,7 +313,30 @@ export function createAgentsHouseAdapter(options) {
244
313
  await keep(upload);
245
314
  return { body: { ...head, paths, upload, runtime: notifications.runtime } };
246
315
  }
247
- 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) {
248
340
  const inline = { ...head, changes, runtime: notifications.runtime };
249
341
  const prepared = uploadable(inline, bytes, declared())
250
342
  ? await uploaded(head, changes, delta, bytes, entry, handed, keep)
@@ -252,14 +344,61 @@ export function createAgentsHouseAdapter(options) {
252
344
  if ('outcome' in prepared)
253
345
  return prepared.outcome;
254
346
  const answer = await transport.door(DOOR_BATCH, prepared.body);
255
- 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
+ });
256
392
  }
257
393
  return {
258
394
  async authorize() {
259
395
  return options.authorize();
260
396
  },
261
- async discover(_authorization) {
262
- 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
+ });
263
402
  const failed = discoveryFailure(answer.status, answer.body);
264
403
  if (failed !== null)
265
404
  return failed;
@@ -268,26 +407,42 @@ export function createAgentsHouseAdapter(options) {
268
407
  return { kind: 'transport-uncertainty' };
269
408
  bounds = document.bounds;
270
409
  const entries = trackedFrom(document);
271
- 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);
272
425
  for (const entry of entries)
273
426
  tracked.set(entry.locator, entry);
274
427
  for (const locator of handed.keys())
275
428
  if (!tracked.has(locator))
276
429
  handed.delete(locator);
277
- notifications.track(entries);
430
+ notifications.track([...tracked.values()]);
278
431
  return {
279
- ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document)),
432
+ ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions, [...tracked.values()])),
280
433
  member: document.member,
434
+ ...(covered === null ? {} : { covered }),
281
435
  };
282
436
  },
283
437
  async bootstrap(_authorization, request) {
284
438
  const entry = authorityFor(request.locator);
285
439
  if (entry === undefined)
286
440
  return { kind: 'room-not-readable', locator: request.locator };
441
+ const asked = { entry, held: request.held };
287
442
  if (request.pageToken !== undefined) {
288
- return continuedBootstrap(entry, JSON.parse(request.pageToken));
443
+ return continuedBootstrap(asked, JSON.parse(request.pageToken));
289
444
  }
290
- return bootstrapTogether(entry);
445
+ return bootstrapTogether(asked);
291
446
  },
292
447
  async refresh(_authorization, request) {
293
448
  const entry = authorityFor(request.locator);
@@ -314,10 +469,11 @@ export function createAgentsHouseAdapter(options) {
314
469
  const delta = deltaBody(mapped.changes);
315
470
  const bytes = deltaBytes(delta);
316
471
  const handed = request.upload;
317
- 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);
318
474
  if (handed === undefined || !lapsedGrant(outcome))
319
475
  return adopted(entry, outcome);
320
- 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));
321
477
  },
322
478
  async history(_authorization, request) {
323
479
  const entry = authorityFor(request.locator);
@@ -334,6 +490,38 @@ export function createAgentsHouseAdapter(options) {
334
490
  ...(request.revision === undefined ? {} : { revision: request.revision }),
335
491
  });
336
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
+ },
337
525
  reached(locator, version, target) {
338
526
  return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
339
527
  },
@@ -343,7 +531,16 @@ export function createAgentsHouseAdapter(options) {
343
531
  async action(_authorization, request) {
344
532
  return withAt(await houseAction(transport, request));
345
533
  },
346
- 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
+ },
347
544
  conversations: notifications.conversations,
348
545
  serviceMode: notifications.serviceMode,
349
546
  };
@@ -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
  };
@@ -65,7 +66,7 @@ export declare function siteTracked(document: HouseSiteDiscovery): TrackedAuthor
65
66
  export declare function trackedFrom(document: HouseDiscoveryDocument): TrackedAuthority[];
66
67
  export declare function providerLimits(bounds: HouseBounds): readonly ProviderLimit[];
67
68
  export declare function declaredRetention(bounds: HouseBounds): Retention;
68
- export declare function capabilities(document: HouseDiscoveryDocument): readonly Capability[];
69
+ export declare function capabilities(actions: HouseDiscoveryDocument['actions'], rooms: readonly Pick<TrackedAuthority, 'authority' | 'houseModel'>[]): readonly Capability[];
69
70
  export declare function siteCapabilities(): readonly Capability[];
70
71
  export declare function discoveredRoom(authority: HouseAuthority): DiscoveredRoom;
71
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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"}
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
  ];
@@ -215,21 +216,24 @@ export function declaredRetention(bounds) {
215
216
  refreshHorizon: bounds.change_retention_seconds,
216
217
  };
217
218
  }
218
- export function capabilities(document) {
219
- const house = document.authorities.find((authority) => authority.houseModel !== null);
219
+ export function capabilities(actions, rooms) {
220
+ const house = rooms.find((room) => room.houseModel !== null);
220
221
  return [
221
222
  {
222
223
  name: PROVIDER_ACTIONS,
223
- actions: declaredActions(document.actions),
224
+ actions: declaredActions(actions),
224
225
  ...(house === undefined
225
226
  ? {}
226
- : { conversationAgents: houseConversationAgents(house.roomRef) }),
227
+ : { conversationAgents: houseConversationAgents(house.authority) }),
227
228
  },
228
229
  ];
229
230
  }
230
231
  export function siteCapabilities() {
231
232
  return [
232
- { name: PROVIDER_ACTIONS, actions: [{ name: 'submit', inputShape: {}, resultKind: 'house' }] },
233
+ {
234
+ name: PROVIDER_ACTIONS,
235
+ actions: [{ name: SITE_SUBMIT, inputShape: {}, resultKind: 'house' }],
236
+ },
233
237
  ];
234
238
  }
235
239
  export function discoveredRoom(authority) {
@@ -0,0 +1,15 @@
1
+ import type { HeldRoom, MutationResult } from '@agentshouse/mdruntime';
2
+ import type { HouseScope, HouseView } from './read.js';
3
+ import type { StreamedAdvancement } from './stream.js';
4
+ export type HandedRoom = {
5
+ roomVersion: string;
6
+ revisions: ReadonlyMap<string, string>;
7
+ };
8
+ export declare function scopeHanded(scope: HouseScope): HandedRoom;
9
+ export declare function heldHanded(held: HeldRoom): HandedRoom;
10
+ export declare function acceptedHanded(held: HandedRoom, accepted: Extract<MutationResult, {
11
+ kind: 'accepted';
12
+ }>, roomVersion: string): HandedRoom;
13
+ export declare function enumeratedHanded(view: HouseView, earlier: HandedRoom | undefined): HandedRoom;
14
+ export declare function streamedHanded(held: HandedRoom, advanced: StreamedAdvancement): HandedRoom;
15
+ //# sourceMappingURL=handed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handed.d.ts","sourceRoot":"","sources":["../src/handed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGvD,MAAM,MAAM,UAAU,GAAG;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC,CAAC;AAEF,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CASzD;AAOD,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAErD;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,OAAO,CAAC,cAAc,EAAE;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,EACvD,WAAW,EAAE,MAAM,GAClB,UAAU,CAKZ;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,CAM7F;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,mBAAmB,GAAG,UAAU,CAM1F"}
package/dist/handed.js ADDED
@@ -0,0 +1,38 @@
1
+ import { roomVersion } from './version.js';
2
+ export function scopeHanded(scope) {
3
+ return {
4
+ roomVersion: roomVersion(scope),
5
+ revisions: new Map(scope.files.flatMap(({ path, revision }) => revision === undefined ? [] : [[path, revision]])),
6
+ };
7
+ }
8
+ function revised(revisions, path, revision) {
9
+ if (revision === undefined)
10
+ revisions.delete(path);
11
+ else
12
+ revisions.set(path, revision);
13
+ }
14
+ export function heldHanded(held) {
15
+ return { roomVersion: held.roomVersion, revisions: new Map(Object.entries(held.revisions)) };
16
+ }
17
+ export function acceptedHanded(held, accepted, roomVersion) {
18
+ const revisions = new Map(held.revisions);
19
+ for (const [path, revision] of Object.entries(accepted.revisions))
20
+ revisions.set(path, revision);
21
+ for (const path of accepted.removals)
22
+ revisions.delete(path);
23
+ return { roomVersion, revisions };
24
+ }
25
+ export function enumeratedHanded(view, earlier) {
26
+ const revisions = new Map(earlier?.revisions);
27
+ for (const change of view.changes) {
28
+ revised(revisions, change.path, 'removed' in change ? undefined : change.revision);
29
+ }
30
+ return { roomVersion: roomVersion(view), revisions };
31
+ }
32
+ export function streamedHanded(held, advanced) {
33
+ const revisions = new Map(held.revisions);
34
+ for (const entry of advanced.entries) {
35
+ revised(revisions, entry.path, entry.kind === 'removed' ? undefined : entry.revision);
36
+ }
37
+ return { roomVersion: advanced.page.roomVersion, revisions };
38
+ }