@agentshouse/mdruntime-adapter 0.1.0-ce1f911b3b195 → 0.1.0-ce3ced044cab5

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 (61) hide show
  1. package/README.md +2 -2
  2. package/dist/actions.d.ts +6 -23
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +23 -134
  5. package/dist/adapter.d.ts.map +1 -1
  6. package/dist/adapter.js +241 -209
  7. package/dist/app-reference.d.ts +2 -0
  8. package/dist/app-reference.d.ts.map +1 -0
  9. package/dist/app-reference.js +2 -0
  10. package/dist/crockford.d.ts +4 -0
  11. package/dist/crockford.d.ts.map +1 -0
  12. package/dist/crockford.js +3 -0
  13. package/dist/discovery.d.ts +6 -10
  14. package/dist/discovery.d.ts.map +1 -1
  15. package/dist/discovery.js +38 -34
  16. package/dist/gather.d.ts +1 -2
  17. package/dist/gather.d.ts.map +1 -1
  18. package/dist/gather.js +5 -15
  19. package/dist/house-model.d.ts +2 -0
  20. package/dist/house-model.d.ts.map +1 -0
  21. package/dist/house-model.js +486 -0
  22. package/dist/index.d.ts +8 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +7 -1
  25. package/dist/model.d.ts +5 -1
  26. package/dist/model.d.ts.map +1 -1
  27. package/dist/model.js +11 -13
  28. package/dist/part.d.ts +14 -0
  29. package/dist/part.d.ts.map +1 -0
  30. package/dist/part.js +90 -0
  31. package/dist/read.d.ts +54 -25
  32. package/dist/read.d.ts.map +1 -1
  33. package/dist/read.js +264 -150
  34. package/dist/room-stream.d.ts +19 -0
  35. package/dist/room-stream.d.ts.map +1 -0
  36. package/dist/room-stream.js +57 -0
  37. package/dist/room-types.d.ts +7 -0
  38. package/dist/room-types.d.ts.map +1 -0
  39. package/dist/room-types.js +125 -0
  40. package/dist/site.d.ts.map +1 -1
  41. package/dist/site.js +67 -115
  42. package/dist/stream.d.ts +5 -5
  43. package/dist/stream.d.ts.map +1 -1
  44. package/dist/stream.js +66 -39
  45. package/dist/transport.d.ts +36 -14
  46. package/dist/transport.d.ts.map +1 -1
  47. package/dist/transport.js +2 -1
  48. package/dist/upload.d.ts +12 -0
  49. package/dist/upload.d.ts.map +1 -0
  50. package/dist/upload.js +63 -0
  51. package/dist/write.d.ts +16 -1
  52. package/dist/write.d.ts.map +1 -1
  53. package/dist/write.js +101 -55
  54. package/package.json +8 -4
  55. package/AGENTS.md +0 -303
  56. package/dist/handed.d.ts +0 -15
  57. package/dist/handed.d.ts.map +0 -1
  58. package/dist/handed.js +0 -38
  59. package/dist/referrer.d.ts +0 -7
  60. package/dist/referrer.d.ts.map +0 -1
  61. package/dist/referrer.js +0 -35
package/dist/adapter.js CHANGED
@@ -1,46 +1,49 @@
1
+ import { stepped } from '@agentshouse/mdruntime';
1
2
  import { DISCOVER_PREFIXES, capabilities, discoveredRoom, mapDiscovery, parseDiscovery, trackedFrom, } from './discovery.js';
2
- import { partModels, streamedModels, viewModels } from './model.js';
3
- import { gathered, gatheredEach, selections } from './gather.js';
3
+ import { streamedModels, viewModels } from './model.js';
4
+ import { gathered, selections } from './gather.js';
4
5
  import { createSiteAdapter } from './site.js';
5
- import { bootstrapOf, contentPrefixes, matchesScope, parseEnumeration, parseEnvelope, parseGrant, refreshFrom, viewOf, wholeView, } from './read.js';
6
+ import { authorityRoot, bootstrapping, parseEnumeration, parseFileAddress, parseGrant, refreshFrom, viewOf, wholeView, } from './read.js';
7
+ import { roomStreams } from './room-stream.js';
6
8
  import { sha256Hex } from './digest.js';
7
- import { acceptedHanded, enumeratedHanded, heldHanded, scopeHanded, streamedHanded, } from './handed.js';
9
+ import { finishedUpload, houseUpload } from './upload.js';
8
10
  import { houseAction } from './actions.js';
9
11
  import { classifyDoor, classifyRead, discoveryFailure, doorRefusal } from './refusal.js';
10
12
  import { houseNotifications } from './stream.js';
11
- import { DOOR_BATCH, DOOR_BATCH_UPLOAD, DOOR_BOOTSTRAP, DOOR_DISCOVER, DOOR_ENUMERATE, DOOR_FILES_GET, DOOR_REVISIONS, } from './transport.js';
12
- import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
13
- import { houseReferrer } from './referrer.js';
13
+ import { DOOR_BATCH, DOOR_BATCH_UPLOAD, DOOR_BOOTSTRAP, DOOR_DISCOVER, DOOR_ENUMERATE, DOOR_FILES_ATTACH, DOOR_FILES_GET, DOOR_REVISIONS, } from './transport.js';
14
+ import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, houseMove, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
15
+ import { HOUSE_NAMEABLE_ROOM_TYPES, HOUSE_ROOM_TYPES } from './room-types.js';
14
16
  import { followsVersion, logPosition, presented, reachedVersion, roomVersion, } from './version.js';
15
17
  const UNCERTAIN = { kind: 'transport-uncertainty' };
16
18
  const UNCERTAIN_READ = { kind: 'transport-uncertainty' };
19
+ const OCTET_STREAM = 'application/octet-stream';
20
+ const EXPIRED_ADDRESS = 403;
17
21
  function lapsedGrant(outcome) {
18
22
  return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
19
23
  }
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 };
24
+ async function heldFiles(authority, held, bound) {
25
+ const listed = await held.files(null, bound + 1);
26
+ const files = listed.slice(0, bound);
27
+ const through = listed.length > bound ? (files.at(-1)?.path ?? null) : null;
28
+ return files.length === 0 ? null : { room: authority, through, files };
30
29
  }
31
30
  async function reused(scope, held) {
32
31
  const sent = new Set(scope.contents.map((entry) => entry.path));
33
32
  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) {
33
+ const asked = scope.files.filter((file) => file.binary === undefined && !sent.has(file.path));
34
+ if (asked.length === 0)
35
+ return scope;
36
+ if (held === undefined)
37
+ return null;
38
+ const kept = await held.contents(asked.map(({ path }) => path));
39
+ for (const file of asked) {
40
+ const found = kept.get(file.path);
41
+ if (found === undefined ||
42
+ found.revision !== file.revision ||
43
+ (await sha256Hex(found.content)) !== file.sha256) {
41
44
  return null;
42
45
  }
43
- contents.push({ path: file.path, content });
46
+ contents.push({ path: file.path, content: found.content });
44
47
  }
45
48
  return { ...scope, contents };
46
49
  }
@@ -51,48 +54,52 @@ export function createAgentsHouseAdapter(options) {
51
54
  const notifications = houseNotifications(transport, 'credential');
52
55
  const tracked = new Map();
53
56
  const handed = new Map();
57
+ const rooms = roomStreams('credential');
58
+ const unreused = new Set();
59
+ let heldRevisions;
54
60
  let bounds;
55
61
  function declared() {
56
62
  if (bounds === undefined)
57
63
  throw new Error('no Discovery declared the bounds');
58
64
  return bounds;
59
65
  }
60
- async function bootstrappedRoom(envelope, sent, asked, continued) {
61
- const { entry } = asked;
62
- if (sent === undefined)
63
- return { kind: 'room-not-readable', locator: entry.locator };
64
- const scope = await reused(sent, asked.held);
66
+ async function streamedPage(entry, token, held) {
67
+ const part = await rooms.part(entry, token);
68
+ if ('kind' in part)
69
+ return part;
70
+ const scope = await reused(part.scope, held);
65
71
  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);
72
+ unreused.add(entry.locator);
73
+ return UNCERTAIN_READ;
71
74
  }
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));
75
+ if (part.first && part.ended)
76
+ handed.set(entry.locator, roomVersion(scope));
76
77
  else
77
78
  handed.delete(entry.locator);
78
79
  notifications.reach(entry.authority, scope);
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
80
  return {
88
- ...bootstrapOf(scope),
89
- ...(models === undefined ? {} : { models }),
90
- ...(continuation === null ? {} : { pageToken: JSON.stringify(continuation) }),
81
+ ...(await stepped(bootstrapping(scope))),
82
+ ...(part.models === undefined ? {} : { models: part.models }),
83
+ ...(part.pageToken === undefined ? {} : { pageToken: part.pageToken }),
84
+ bytes: part.bytes,
91
85
  };
92
86
  }
87
+ async function openedRoom(entry, request) {
88
+ const reusing = unreused.delete(entry.locator) ? undefined : request.held;
89
+ const files = reusing === undefined
90
+ ? null
91
+ : await heldFiles(entry.authority, reusing, declared().max_held_files);
92
+ const answered = await transport.streamed(DOOR_BOOTSTRAP, {
93
+ prefixes: [authorityRoot(entry.handle)],
94
+ ...(files === null ? {} : { held: [files] }),
95
+ }, request.signal ?? new AbortController().signal);
96
+ if (answered.room === null)
97
+ return classifyDoor(answered.status, answered.body, entry.locator) ?? UNCERTAIN_READ;
98
+ return rooms.head(entry, answered.room, request.arriving, request.signal);
99
+ }
93
100
  async function enumeratedPage(ask, page) {
94
101
  const answer = await transport.door(DOOR_ENUMERATE, {
95
- prefixes: contentPrefixes(ask.entry),
102
+ prefixes: [authorityRoot(ask.entry.handle)],
96
103
  positions: [
97
104
  {
98
105
  ...presented(ask.entry.authority, { position: page.after, logEpoch: ask.from.logEpoch }),
@@ -116,52 +123,54 @@ export function createAgentsHouseAdapter(options) {
116
123
  if ('kind' in view)
117
124
  return view;
118
125
  notifications.reach(ask.entry.authority, view);
119
- const answer = refreshFrom(view, ask.fromRoomVersion);
126
+ const answer = await refreshFrom(view, ask.fromRoomVersion);
120
127
  if (answer.kind !== 'net-advancement')
121
128
  return answer;
122
- handed.set(ask.entry.locator, enumeratedHanded(view, handed.get(ask.entry.locator)));
129
+ handed.set(ask.entry.locator, roomVersion(view));
123
130
  const models = await viewModels(view, ask.entry);
124
131
  return models === undefined ? answer : { ...answer, models };
125
132
  }
126
133
  async function streamed(entry, request, from) {
127
- const held = handed.get(entry.locator);
128
- if (held?.roomVersion !== request.fromRoomVersion)
134
+ if (handed.get(entry.locator) !== request.fromRoomVersion)
129
135
  return null;
130
- const advanced = notifications.advance(entry, from, (path, revision) => held.revisions.get(path) === revision);
136
+ const asked = notifications.edited(entry, from);
137
+ const kept = asked.length === 0 || heldRevisions === undefined
138
+ ? new Map()
139
+ : await heldRevisions(entry.locator, asked);
140
+ const advanced = await stepped(notifications.advance(entry, from, (path, revision) => kept.get(path) === revision));
131
141
  if (advanced === null)
132
142
  return null;
133
- handed.set(entry.locator, streamedHanded(held, advanced));
143
+ handed.set(entry.locator, advanced.page.roomVersion);
134
144
  const models = await streamedModels(advanced.schemas, entry, 'credential');
135
145
  return models === undefined ? advanced.page : { ...advanced.page, models };
136
146
  }
137
- function held(rooms, demand) {
147
+ function held(rooms) {
138
148
  const kept = rooms.flatMap((room) => {
139
149
  const entry = tracked.get(room.locator);
140
150
  const at = logPosition(room.roomVersion);
141
151
  if (entry === undefined || at === null)
142
152
  return [];
143
- handed.set(room.locator, heldHanded(room));
153
+ handed.set(room.locator, room.roomVersion);
144
154
  notifications.reach(entry.authority, at);
145
155
  return [presented(entry.authority, at)];
146
156
  });
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
- });
157
+ notifications.send(kept);
158
+ }
159
+ async function adoptedMove(entry, into, outcome) {
160
+ if (outcome.kind === 'accepted' && outcome.destination !== undefined) {
161
+ await adopted(into, { kind: 'accepted', ...outcome.destination });
162
+ }
163
+ return adopted(entry, outcome);
156
164
  }
157
- function adopted(entry, outcome) {
165
+ async function adopted(entry, outcome) {
158
166
  if (outcome.kind !== 'accepted')
159
167
  return outcome;
160
168
  const held = handed.get(entry.locator);
161
169
  if (held === undefined)
162
170
  return outcome;
163
- const follows = followsVersion(held.roomVersion, outcome.roomVersion);
164
- handed.set(entry.locator, acceptedHanded(held, outcome, follows ? outcome.roomVersion : held.roomVersion));
171
+ const follows = followsVersion(held, outcome.roomVersion);
172
+ if (follows)
173
+ handed.set(entry.locator, outcome.roomVersion);
165
174
  const at = logPosition(outcome.roomVersion);
166
175
  if (follows && at !== null)
167
176
  notifications.reach(entry.authority, at);
@@ -184,68 +193,6 @@ export function createAgentsHouseAdapter(options) {
184
193
  return result;
185
194
  return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
186
195
  }
187
- function weight(entry) {
188
- return contentPrefixes(entry).length;
189
- }
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));
222
- }
223
- }
224
- open = unreached;
225
- }
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);
248
- }
249
196
  function layered(asks) {
250
197
  const depths = new Map();
251
198
  const layers = [];
@@ -258,12 +205,12 @@ export function createAgentsHouseAdapter(options) {
258
205
  }
259
206
  async function enumerated(asks) {
260
207
  const answers = [];
261
- const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, ({ ask }) => weight(ask.entry), declared().max_prefixes));
208
+ const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, declared().max_prefixes));
262
209
  for (const part of parts) {
263
210
  let open = [...part];
264
211
  while (open.length > 0) {
265
212
  const answer = await transport.door(DOOR_ENUMERATE, {
266
- prefixes: open.flatMap(({ ask }) => contentPrefixes(ask.entry)),
213
+ prefixes: open.map(({ ask }) => authorityRoot(ask.entry.handle)),
267
214
  positions: open.map(({ ask }) => presented(ask.entry.authority, ask.from)),
268
215
  });
269
216
  const classified = open.map(({ ask }) => classifyRead(answer.status, answer.body, ask.entry.locator));
@@ -292,7 +239,6 @@ export function createAgentsHouseAdapter(options) {
292
239
  }
293
240
  return answers;
294
241
  }
295
- const bootstrapTogether = gatheredEach(bootstrapped);
296
242
  const enumerateTogether = gathered(enumerated);
297
243
  async function uploaded(head, changes, delta, bytes, entry, handed, keep) {
298
244
  const paths = batchPaths(changes);
@@ -302,10 +248,12 @@ export function createAgentsHouseAdapter(options) {
302
248
  }
303
249
  const minted = await transport.door(DOOR_BATCH_UPLOAD, { ...head, paths, bytes, sha256 });
304
250
  if (minted.status !== 200) {
305
- return { outcome: mutationOutcome(minted.status, minted.body, entry.locator, entry.handle) };
251
+ return {
252
+ outcome: await stepped(mutationOutcome(minted.status, minted.body, entry.locator, entry.handle)),
253
+ };
306
254
  }
307
255
  const grant = parseGrant(minted.body);
308
- if (grant === null || grant.method !== 'POST')
256
+ if (grant === null)
309
257
  return { outcome: UNCERTAIN };
310
258
  const delivered = await transport.transfer({
311
259
  act: 'authored_batch_upload',
@@ -315,36 +263,31 @@ export function createAgentsHouseAdapter(options) {
315
263
  if (delivered.status !== 200)
316
264
  return { outcome: UNCERTAIN };
317
265
  const holding = parseGrant(delivered.body);
318
- if (holding === null || holding.method !== 'POST')
266
+ if (holding === null)
319
267
  return { outcome: UNCERTAIN };
320
268
  const upload = { url: holding.url, operation: holding.operation, bytes, sha256 };
321
269
  await keep(upload);
322
270
  return { body: { ...head, paths, upload, runtime: notifications.runtime } };
323
271
  }
324
- async function staged(answer, batch) {
272
+ async function staged(answer, { changeBatch: batch, file }) {
325
273
  const refusal = doorRefusal(answer.body);
326
274
  if (refusal?.code !== 'files_unstaged')
327
275
  return null;
328
- const grants = refusal.fields.upload;
329
- if (!Array.isArray(grants))
276
+ const uploads = refusal.fields.upload;
277
+ if (!Array.isArray(uploads))
330
278
  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)
279
+ for (const named of uploads) {
280
+ const upload = houseUpload(named);
281
+ const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === named.sha256);
282
+ if (upload === null || change === undefined)
335
283
  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)
284
+ const finished = await finishedUpload(transport, upload, await file(change.localId));
285
+ if (finished?.body?.staged !== true)
343
286
  return false;
344
287
  }
345
288
  return true;
346
289
  }
347
- async function submit(batch, head, changes, delta, bytes, entry, handed, keep) {
290
+ async function submit(request, head, changes, delta, bytes, entry, handed, keep, into) {
348
291
  const inline = { ...head, changes, runtime: notifications.runtime };
349
292
  const prepared = uploadable(inline, bytes, declared())
350
293
  ? await uploaded(head, changes, delta, bytes, entry, handed, keep)
@@ -352,15 +295,20 @@ export function createAgentsHouseAdapter(options) {
352
295
  if ('outcome' in prepared)
353
296
  return prepared.outcome;
354
297
  const answer = await transport.door(DOOR_BATCH, prepared.body);
355
- const stagedFiles = await staged(answer, batch);
298
+ const stagedFiles = await staged(answer, request);
356
299
  if (stagedFiles === false)
357
300
  return UNCERTAIN;
358
301
  const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
359
- if (stagedFiles === true && (await staged(settled, batch)) !== null)
302
+ if (stagedFiles === true && (await staged(settled, request)) !== null)
360
303
  return UNCERTAIN;
361
- return mutationOutcome(settled.status, settled.body, entry.locator, entry.handle);
304
+ return stepped(mutationOutcome(settled.status, settled.body, entry.locator, entry.handle, into === undefined
305
+ ? undefined
306
+ : {
307
+ source: { authority: entry.authority, handle: entry.handle },
308
+ destination: { authority: into.authority, handle: into.handle },
309
+ }));
362
310
  }
363
- async function downloaded(answer, locator) {
311
+ function addressed(answer, locator) {
364
312
  const failure = classifyDoor(answer.status, answer.body, locator);
365
313
  if (failure !== null) {
366
314
  return failure.kind === 'scoped-refusal'
@@ -372,20 +320,69 @@ export function createAgentsHouseAdapter(options) {
372
320
  }
373
321
  : failure;
374
322
  }
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)
323
+ const address = parseFileAddress(answer.body);
324
+ return address === null ? UNCERTAIN_READ : { kind: 'address', ...address };
325
+ }
326
+ function linked(answer, locator) {
327
+ const address = addressed(answer, locator);
328
+ return address.kind === 'address'
329
+ ? { kind: 'link', url: address.url, name: address.name, mediaType: address.mediaType }
330
+ : address;
331
+ }
332
+ function refusedAttach(answer, locator) {
333
+ const failure = classifyDoor(answer.status, answer.body, locator);
334
+ if (failure === null)
335
+ return null;
336
+ return failure.kind === 'scoped-refusal'
337
+ ? {
338
+ kind: 'rejected',
339
+ code: failure.code,
340
+ fields: failure.fields,
341
+ retryable: failure.retryable,
342
+ }
343
+ : failure;
344
+ }
345
+ async function downloaded(ask, locator) {
346
+ let address = addressed(await ask(), locator);
347
+ if (address.kind !== 'address')
348
+ return address;
349
+ let read = await transport.download(address.url);
350
+ if (read.status === EXPIRED_ADDRESS) {
351
+ address = addressed(await ask(), locator);
352
+ if (address.kind !== 'address')
353
+ return address;
354
+ read = await transport.download(address.url);
355
+ }
356
+ if (read.status !== 200 || read.body === null)
380
357
  return UNCERTAIN_READ;
381
- return { kind: 'binary', blob: read.blob };
358
+ return {
359
+ kind: 'binary',
360
+ body: read.body,
361
+ mediaType: address.mediaType,
362
+ sha256: address.sha256,
363
+ };
364
+ }
365
+ function pathAddress(request) {
366
+ const entry = authorityFor(request.locator);
367
+ if (entry === undefined)
368
+ return null;
369
+ return () => transport.door(DOOR_FILES_GET, {
370
+ room_ref: entry.authority,
371
+ path: request.path,
372
+ revision: request.revision,
373
+ });
382
374
  }
383
375
  return {
376
+ roomTypes: HOUSE_ROOM_TYPES,
377
+ nameableRoomTypes: HOUSE_NAMEABLE_ROOM_TYPES,
384
378
  async authorize() {
385
379
  return options.authorize();
386
380
  },
387
- async discover(_authorization) {
388
- const answer = await transport.door(DOOR_DISCOVER, { prefixes: [...DISCOVER_PREFIXES] });
381
+ async discover(_authorization, scope) {
382
+ const answer = await transport.door(DOOR_DISCOVER, {
383
+ prefixes: [...DISCOVER_PREFIXES],
384
+ ...(scope === undefined ? {} : { rooms: scope.authorities }),
385
+ });
389
386
  const failed = discoveryFailure(answer.status, answer.body);
390
387
  if (failed !== null)
391
388
  return failed;
@@ -394,27 +391,40 @@ export function createAgentsHouseAdapter(options) {
394
391
  return { kind: 'transport-uncertainty' };
395
392
  bounds = document.bounds;
396
393
  const entries = trackedFrom(document);
397
- tracked.clear();
394
+ const covered = scope === undefined
395
+ ? null
396
+ : [
397
+ ...new Set([
398
+ ...[...tracked.values()]
399
+ .filter((entry) => scope.authorities.includes(entry.authority))
400
+ .map((entry) => entry.locator),
401
+ ...entries.map((entry) => entry.locator),
402
+ ]),
403
+ ];
404
+ if (covered === null)
405
+ tracked.clear();
406
+ else
407
+ for (const locator of covered)
408
+ tracked.delete(locator);
398
409
  for (const entry of entries)
399
410
  tracked.set(entry.locator, entry);
400
411
  for (const locator of handed.keys())
401
412
  if (!tracked.has(locator))
402
413
  handed.delete(locator);
403
- notifications.track(entries);
414
+ notifications.track([...tracked.values()]);
404
415
  return {
405
- ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document)),
416
+ ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions, [...tracked.values()])),
406
417
  member: document.member,
418
+ ...(covered === null ? {} : { covered }),
407
419
  };
408
420
  },
409
421
  async bootstrap(_authorization, request) {
410
422
  const entry = authorityFor(request.locator);
411
423
  if (entry === undefined)
412
424
  return { kind: 'room-not-readable', locator: request.locator };
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);
425
+ return request.pageToken === undefined
426
+ ? openedRoom(entry, request)
427
+ : streamedPage(entry, request.pageToken, request.held);
418
428
  },
419
429
  async refresh(_authorization, request) {
420
430
  const entry = authorityFor(request.locator);
@@ -434,18 +444,27 @@ export function createAgentsHouseAdapter(options) {
434
444
  const entry = authorityFor(request.locator);
435
445
  if (entry === undefined)
436
446
  return { kind: 'room-not-readable', locator: request.locator };
437
- const mapped = houseChanges(request.changeBatch, entry.handle);
447
+ const batch = request.changeBatch;
448
+ const destination = batch.between?.destination.room;
449
+ const into = destination === undefined ? undefined : authorityFor(destination);
450
+ if (destination !== undefined && into === undefined) {
451
+ return { kind: 'room-not-readable', locator: destination };
452
+ }
453
+ const mapped = into === undefined
454
+ ? houseChanges(batch, entry.handle)
455
+ : houseMove(batch, entry.handle, into.handle);
438
456
  if (mapped.kind === 'unmapped')
439
457
  return rejectedMapping(mapped);
440
- const head = operationHead(request.changeBatch);
458
+ const head = operationHead(batch);
441
459
  const delta = deltaBody(mapped.changes);
442
460
  const bytes = deltaBytes(delta);
443
461
  const handed = request.upload;
444
- const batch = request.changeBatch;
445
- const outcome = await submit(batch, head, mapped.changes, delta, bytes, entry, handed, keep);
462
+ const answer = (upload) => submit(request, head, mapped.changes, delta, bytes, entry, upload, keep, into);
463
+ const settle = (outcome) => into === undefined ? adopted(entry, outcome) : adoptedMove(entry, into, outcome);
464
+ const outcome = await answer(handed);
446
465
  if (handed === undefined || !lapsedGrant(outcome))
447
- return adopted(entry, outcome);
448
- return adopted(entry, await submit(batch, head, mapped.changes, delta, bytes, entry, undefined, keep));
466
+ return settle(outcome);
467
+ return settle(await answer(undefined));
449
468
  },
450
469
  async history(_authorization, request) {
451
470
  const entry = authorityFor(request.locator);
@@ -463,35 +482,45 @@ export function createAgentsHouseAdapter(options) {
463
482
  });
464
483
  },
465
484
  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);
485
+ const ask = pathAddress(request);
486
+ if (ask === null)
487
+ return { kind: 'room-not-readable', locator: request.locator };
488
+ return downloaded(ask, request.locator);
489
+ },
490
+ async link(_authorization, request) {
491
+ const ask = pathAddress(request);
492
+ if (ask === null)
493
+ return { kind: 'room-not-readable', locator: request.locator };
494
+ return linked(await ask(), request.locator);
486
495
  },
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 }) };
496
+ async attach(_authorization, request) {
497
+ const entry = authorityFor(request.locator);
498
+ if (entry === undefined)
499
+ return { kind: 'room-not-readable', locator: request.locator };
500
+ const declared = await transport.door(DOOR_FILES_ATTACH, {
501
+ room_ref: entry.authority,
502
+ name: request.name,
503
+ bytes: request.byteLength,
504
+ sha256: request.sha256,
505
+ media_type: request.mediaType.length > 0 ? request.mediaType : OCTET_STREAM,
506
+ });
507
+ const refused = refusedAttach(declared, request.locator);
508
+ if (refused !== null)
509
+ return refused;
510
+ const upload = houseUpload(declared.body?.upload);
511
+ if (upload === null)
512
+ return UNCERTAIN_READ;
513
+ const finished = await finishedUpload(transport, upload, request.blob);
514
+ if (finished === null)
515
+ return UNCERTAIN_READ;
516
+ const unfinished = refusedAttach(finished, request.locator);
517
+ if (unfinished !== null)
518
+ return unfinished;
519
+ const { path, revision } = (finished.body ?? {});
520
+ const root = `/rooms/${entry.handle}/`;
521
+ return typeof path === 'string' && path.startsWith(root) && typeof revision === 'string'
522
+ ? { kind: 'attached', path: path.slice(root.length), revision }
523
+ : UNCERTAIN_READ;
495
524
  },
496
525
  reached(locator, version, target) {
497
526
  return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
@@ -505,7 +534,10 @@ export function createAgentsHouseAdapter(options) {
505
534
  roomStream: {
506
535
  hold: held,
507
536
  reopen: notifications.reopen,
508
- observe: notifications.observe,
537
+ observe(notify, receipts, revisions) {
538
+ heldRevisions = revisions;
539
+ return notifications.observe(notify, receipts);
540
+ },
509
541
  published(locator, roomVersion) {
510
542
  const at = logPosition(roomVersion);
511
543
  if (at !== null)
@@ -0,0 +1,2 @@
1
+ export declare const APP_REFERENCE_PATTERN: RegExp;
2
+ //# sourceMappingURL=app-reference.d.ts.map