@agentshouse/mdruntime-adapter 0.1.0-cbbc9b36e7fd4 → 0.1.0-cbc5a1c3e1e08

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 (58) hide show
  1. package/README.md +2 -2
  2. package/dist/actions.d.ts +10 -21
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +68 -133
  5. package/dist/adapter.d.ts.map +1 -1
  6. package/dist/adapter.js +248 -170
  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 +37 -32
  16. package/dist/gather.d.ts +1 -1
  17. package/dist/gather.d.ts.map +1 -1
  18. package/dist/gather.js +4 -14
  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 +484 -0
  22. package/dist/index.d.ts +7 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +6 -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 +15 -0
  29. package/dist/part.d.ts.map +1 -0
  30. package/dist/part.js +82 -0
  31. package/dist/read.d.ts +58 -24
  32. package/dist/read.d.ts.map +1 -1
  33. package/dist/read.js +270 -144
  34. package/dist/room-types.d.ts +7 -0
  35. package/dist/room-types.d.ts.map +1 -0
  36. package/dist/room-types.js +124 -0
  37. package/dist/site.d.ts.map +1 -1
  38. package/dist/site.js +98 -107
  39. package/dist/stream.d.ts +5 -5
  40. package/dist/stream.d.ts.map +1 -1
  41. package/dist/stream.js +66 -39
  42. package/dist/transport.d.ts +32 -16
  43. package/dist/transport.d.ts.map +1 -1
  44. package/dist/transport.js +1 -1
  45. package/dist/upload.d.ts +12 -0
  46. package/dist/upload.d.ts.map +1 -0
  47. package/dist/upload.js +63 -0
  48. package/dist/write.d.ts +12 -1
  49. package/dist/write.d.ts.map +1 -1
  50. package/dist/write.js +93 -55
  51. package/package.json +8 -4
  52. package/AGENTS.md +0 -299
  53. package/dist/handed.d.ts +0 -15
  54. package/dist/handed.d.ts.map +0 -1
  55. package/dist/handed.js +0 -38
  56. package/dist/referrer.d.ts +0 -7
  57. package/dist/referrer.d.ts.map +0 -1
  58. package/dist/referrer.js +0 -35
package/dist/adapter.js CHANGED
@@ -1,46 +1,48 @@
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 { manifestedModels, partModels, streamedModels, viewModels } from './model.js';
3
4
  import { gathered, gatheredEach, 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, emptyScope, housePart, manifested, parseEnumeration, parseFileAddress, parseGrant, parseParted, partInventory, refreshFrom, viewOf, wholeView, } from './read.js';
7
+ import { PART_ATTEMPTS, partArrival, partFrame, unverifiedPart } from './part.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
13
  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';
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 EXPIRED_ADDRESS = 403;
17
20
  function lapsedGrant(outcome) {
18
21
  return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
19
22
  }
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 };
23
+ async function heldFiles(authority, held, bound) {
24
+ const listed = await held.files(null, bound + 1);
25
+ const files = listed.slice(0, bound);
26
+ const through = listed.length > bound ? (files.at(-1)?.path ?? null) : null;
27
+ return files.length === 0 ? null : { room: authority, through, files };
30
28
  }
31
29
  async function reused(scope, held) {
32
30
  const sent = new Set(scope.contents.map((entry) => entry.path));
33
31
  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) {
32
+ const asked = scope.files.filter((file) => file.binary === undefined && !sent.has(file.path));
33
+ if (asked.length === 0)
34
+ return scope;
35
+ if (held === undefined)
36
+ return null;
37
+ const kept = await held.contents(asked.map(({ path }) => path));
38
+ for (const file of asked) {
39
+ const found = kept.get(file.path);
40
+ if (found === undefined ||
41
+ found.revision !== file.revision ||
42
+ (await sha256Hex(found.content)) !== file.sha256) {
41
43
  return null;
42
44
  }
43
- contents.push({ path: file.path, content });
45
+ contents.push({ path: file.path, content: found.content });
44
46
  }
45
47
  return { ...scope, contents };
46
48
  }
@@ -51,48 +53,83 @@ export function createAgentsHouseAdapter(options) {
51
53
  const notifications = houseNotifications(transport, 'credential');
52
54
  const tracked = new Map();
53
55
  const handed = new Map();
56
+ let heldRevisions;
54
57
  let bounds;
55
58
  function declared() {
56
59
  if (bounds === undefined)
57
60
  throw new Error('no Discovery declared the bounds');
58
61
  return bounds;
59
62
  }
60
- async function bootstrappedRoom(envelope, sent, asked, continued) {
63
+ async function fetchedPart(entry, continued, listed, received, arriving) {
64
+ if (received !== undefined) {
65
+ const kept = new Uint8Array(received);
66
+ const held = await housePart(continued.room, kept, listed, 'credential');
67
+ if (held !== null)
68
+ return 'kind' in held ? held : { scope: held, frame: kept };
69
+ }
70
+ for (let attempt = 0; attempt < PART_ATTEMPTS; attempt += 1) {
71
+ const answered = await transport.door(DOOR_BOOTSTRAP, {
72
+ prefixes: [authorityRoot(entry.handle)],
73
+ part: listed.part,
74
+ ...(continued.held === undefined ? {} : { held: [continued.held] }),
75
+ }, partArrival(listed, arriving));
76
+ const failure = classifyRead(answered.status, answered.body, entry.locator);
77
+ if (failure !== null)
78
+ return failure;
79
+ const frame = partFrame(answered.body);
80
+ if (frame === null)
81
+ continue;
82
+ const scope = await housePart(continued.room, frame, listed, 'credential');
83
+ if (scope !== null)
84
+ return 'kind' in scope ? scope : { scope, frame };
85
+ }
86
+ return unverifiedPart(entry.locator);
87
+ }
88
+ async function manifestPage(asked, continued) {
89
+ const inventory = partInventory(continued.room);
90
+ if (inventory.parts === 0) {
91
+ const page = await partPage(asked, continued, undefined, undefined);
92
+ return page.kind === 'bootstrap' ? { ...page, inventory } : page;
93
+ }
94
+ const { models, seen } = await manifestedModels(continued.room, asked.entry);
95
+ return manifested(continued.room, inventory, JSON.stringify({ ...continued, seen }), models);
96
+ }
97
+ async function partPage(asked, continued, received, arriving) {
61
98
  const { entry } = asked;
62
- if (sent === undefined)
63
- return { kind: 'room-not-readable', locator: entry.locator };
64
- const scope = await reused(sent, asked.held);
99
+ const listed = continued.room.parts[continued.next];
100
+ const sent = listed === undefined
101
+ ? { scope: emptyScope(continued.room), frame: null }
102
+ : await fetchedPart(entry, continued, listed, received, arriving);
103
+ if ('kind' in sent)
104
+ return sent;
105
+ const scope = await reused(sent.scope, asked.held);
65
106
  if (scope === null) {
66
107
  return asked.held === undefined
67
108
  ? UNCERTAIN_READ
68
- : continued === null
69
- ? bootstrapTogether({ entry, held: undefined })
70
- : continuedBootstrap({ entry, held: undefined }, continued);
109
+ : bootstrapTogether({ entry, held: undefined });
71
110
  }
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));
111
+ const next = continued.next + 1;
112
+ const more = next < continued.room.parts.length;
113
+ if (continued.next === 0 && !more)
114
+ handed.set(entry.locator, roomVersion(scope));
76
115
  else
77
116
  handed.delete(entry.locator);
78
117
  notifications.reach(entry.authority, scope);
79
118
  const seen = [
80
- ...(continued?.seen ?? []),
81
- ...entry.schemas.filter((schema) => scope.files.some((file) => file.path === schema)),
119
+ ...continued.seen,
120
+ ...(scope.files.some((file) => file.path === entry.schema) ? [entry.schema] : []),
82
121
  ];
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 }) };
122
+ const models = await partModels(scope, entry, new Set(seen), !more);
87
123
  return {
88
- ...bootstrapOf(scope),
124
+ ...(await stepped(bootstrapping(scope))),
89
125
  ...(models === undefined ? {} : { models }),
90
- ...(continuation === null ? {} : { pageToken: JSON.stringify(continuation) }),
126
+ ...(more ? { pageToken: JSON.stringify({ ...continued, next, seen }) } : {}),
127
+ ...(sent.frame === null ? {} : { received: sent.frame }),
91
128
  };
92
129
  }
93
130
  async function enumeratedPage(ask, page) {
94
131
  const answer = await transport.door(DOOR_ENUMERATE, {
95
- prefixes: contentPrefixes(ask.entry),
132
+ prefixes: [authorityRoot(ask.entry.handle)],
96
133
  positions: [
97
134
  {
98
135
  ...presented(ask.entry.authority, { position: page.after, logEpoch: ask.from.logEpoch }),
@@ -116,52 +153,54 @@ export function createAgentsHouseAdapter(options) {
116
153
  if ('kind' in view)
117
154
  return view;
118
155
  notifications.reach(ask.entry.authority, view);
119
- const answer = refreshFrom(view, ask.fromRoomVersion);
156
+ const answer = await refreshFrom(view, ask.fromRoomVersion);
120
157
  if (answer.kind !== 'net-advancement')
121
158
  return answer;
122
- handed.set(ask.entry.locator, enumeratedHanded(view, handed.get(ask.entry.locator)));
159
+ handed.set(ask.entry.locator, roomVersion(view));
123
160
  const models = await viewModels(view, ask.entry);
124
161
  return models === undefined ? answer : { ...answer, models };
125
162
  }
126
163
  async function streamed(entry, request, from) {
127
- const held = handed.get(entry.locator);
128
- if (held?.roomVersion !== request.fromRoomVersion)
164
+ if (handed.get(entry.locator) !== request.fromRoomVersion)
129
165
  return null;
130
- const advanced = notifications.advance(entry, from, (path, revision) => held.revisions.get(path) === revision);
166
+ const asked = notifications.edited(entry, from);
167
+ const kept = asked.length === 0 || heldRevisions === undefined
168
+ ? new Map()
169
+ : await heldRevisions(entry.locator, asked);
170
+ const advanced = await stepped(notifications.advance(entry, from, (path, revision) => kept.get(path) === revision));
131
171
  if (advanced === null)
132
172
  return null;
133
- handed.set(entry.locator, streamedHanded(held, advanced));
173
+ handed.set(entry.locator, advanced.page.roomVersion);
134
174
  const models = await streamedModels(advanced.schemas, entry, 'credential');
135
175
  return models === undefined ? advanced.page : { ...advanced.page, models };
136
176
  }
137
- function held(rooms, demand) {
177
+ function held(rooms) {
138
178
  const kept = rooms.flatMap((room) => {
139
179
  const entry = tracked.get(room.locator);
140
180
  const at = logPosition(room.roomVersion);
141
181
  if (entry === undefined || at === null)
142
182
  return [];
143
- handed.set(room.locator, heldHanded(room));
183
+ handed.set(room.locator, room.roomVersion);
144
184
  notifications.reach(entry.authority, at);
145
185
  return [presented(entry.authority, at)];
146
186
  });
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
- });
187
+ notifications.send(kept);
188
+ }
189
+ async function adoptedMove(entry, into, outcome) {
190
+ if (outcome.kind === 'accepted' && outcome.destination !== undefined) {
191
+ await adopted(into, { kind: 'accepted', ...outcome.destination });
192
+ }
193
+ return adopted(entry, outcome);
156
194
  }
157
- function adopted(entry, outcome) {
195
+ async function adopted(entry, outcome) {
158
196
  if (outcome.kind !== 'accepted')
159
197
  return outcome;
160
198
  const held = handed.get(entry.locator);
161
199
  if (held === undefined)
162
200
  return outcome;
163
- const follows = followsVersion(held.roomVersion, outcome.roomVersion);
164
- handed.set(entry.locator, acceptedHanded(held, outcome, follows ? outcome.roomVersion : held.roomVersion));
201
+ const follows = followsVersion(held, outcome.roomVersion);
202
+ if (follows)
203
+ handed.set(entry.locator, outcome.roomVersion);
165
204
  const at = logPosition(outcome.roomVersion);
166
205
  if (follows && at !== null)
167
206
  notifications.reach(entry.authority, at);
@@ -184,68 +223,61 @@ export function createAgentsHouseAdapter(options) {
184
223
  return result;
185
224
  return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
186
225
  }
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) ?? []);
226
+ async function holding(asks) {
227
+ const bound = Math.floor(declared().max_held_files / Math.max(1, asks.filter((ask) => ask.held !== undefined).length));
228
+ const held = [];
229
+ for (const ask of asks) {
230
+ if (ask.held === undefined || bound === 0)
231
+ continue;
232
+ const files = await heldFiles(ask.entry.authority, ask.held, bound);
233
+ if (files !== null)
234
+ held.push(files);
235
+ }
236
+ return held;
193
237
  }
194
238
  async function bootstrapped(asks, answer) {
195
239
  let open = asks.map((asked, ask) => ({ entry: asked.entry, asked, ask }));
196
240
  while (open.length > 0) {
197
241
  const unreached = [];
198
- for (const part of selections(open, ({ entry }) => weight(entry), declared().max_prefixes)) {
199
- const held = holding(part.map(({ asked }) => asked), null);
242
+ for (const part of selections(open, declared().max_prefixes)) {
243
+ const held = await holding(part.map(({ asked }) => asked));
200
244
  const answered = await transport.door(DOOR_BOOTSTRAP, {
201
- prefixes: part.flatMap(({ entry }) => contentPrefixes(entry)),
245
+ prefixes: part.map(({ entry }) => authorityRoot(entry.handle)),
202
246
  ...(held.length === 0 ? {} : { held }),
203
247
  });
204
248
  const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
205
- const envelope = classified[0] === null ? parseEnvelope(answered.body) : null;
249
+ const parted = classified[0] === null ? parseParted(answered.body) : null;
206
250
  for (const [index, asked] of part.entries()) {
207
251
  const failure = classified[index];
208
252
  if (failure !== null && failure !== undefined) {
209
253
  answer(asked.ask, failure);
210
254
  continue;
211
255
  }
212
- if (envelope === null) {
256
+ if (parted === null) {
213
257
  answer(asked.ask, UNCERTAIN_READ);
214
258
  continue;
215
259
  }
216
- const scope = envelope.scopes.find((candidate) => matchesScope(candidate, asked.entry));
217
- if (scope === undefined && envelope.next !== undefined) {
260
+ const room = parted.rooms.find((candidate) => candidate.anchor === asked.entry.authority);
261
+ if (room === undefined && parted.next !== undefined) {
218
262
  unreached.push(asked);
219
263
  continue;
220
264
  }
221
- answer(asked.ask, await bootstrappedRoom(envelope, scope, asked.asked, null));
265
+ if (room === undefined) {
266
+ answer(asked.ask, { kind: 'room-not-readable', locator: asked.entry.locator });
267
+ continue;
268
+ }
269
+ const kept = held.find((files) => files.room === asked.entry.authority);
270
+ answer(asked.ask, await manifestPage(asked.asked, {
271
+ room,
272
+ next: 0,
273
+ seen: [],
274
+ ...(kept === undefined ? {} : { held: kept }),
275
+ }));
222
276
  }
223
277
  }
224
278
  open = unreached;
225
279
  }
226
280
  }
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
281
  function layered(asks) {
250
282
  const depths = new Map();
251
283
  const layers = [];
@@ -258,12 +290,12 @@ export function createAgentsHouseAdapter(options) {
258
290
  }
259
291
  async function enumerated(asks) {
260
292
  const answers = [];
261
- const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, ({ ask }) => weight(ask.entry), declared().max_prefixes));
293
+ const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, declared().max_prefixes));
262
294
  for (const part of parts) {
263
295
  let open = [...part];
264
296
  while (open.length > 0) {
265
297
  const answer = await transport.door(DOOR_ENUMERATE, {
266
- prefixes: open.flatMap(({ ask }) => contentPrefixes(ask.entry)),
298
+ prefixes: open.map(({ ask }) => authorityRoot(ask.entry.handle)),
267
299
  positions: open.map(({ ask }) => presented(ask.entry.authority, ask.from)),
268
300
  });
269
301
  const classified = open.map(({ ask }) => classifyRead(answer.status, answer.body, ask.entry.locator));
@@ -302,10 +334,12 @@ export function createAgentsHouseAdapter(options) {
302
334
  }
303
335
  const minted = await transport.door(DOOR_BATCH_UPLOAD, { ...head, paths, bytes, sha256 });
304
336
  if (minted.status !== 200) {
305
- return { outcome: mutationOutcome(minted.status, minted.body, entry.locator, entry.handle) };
337
+ return {
338
+ outcome: await stepped(mutationOutcome(minted.status, minted.body, entry.locator, entry.handle)),
339
+ };
306
340
  }
307
341
  const grant = parseGrant(minted.body);
308
- if (grant === null || grant.method !== 'POST')
342
+ if (grant === null)
309
343
  return { outcome: UNCERTAIN };
310
344
  const delivered = await transport.transfer({
311
345
  act: 'authored_batch_upload',
@@ -315,36 +349,31 @@ export function createAgentsHouseAdapter(options) {
315
349
  if (delivered.status !== 200)
316
350
  return { outcome: UNCERTAIN };
317
351
  const holding = parseGrant(delivered.body);
318
- if (holding === null || holding.method !== 'POST')
352
+ if (holding === null)
319
353
  return { outcome: UNCERTAIN };
320
354
  const upload = { url: holding.url, operation: holding.operation, bytes, sha256 };
321
355
  await keep(upload);
322
356
  return { body: { ...head, paths, upload, runtime: notifications.runtime } };
323
357
  }
324
- async function staged(answer, batch) {
358
+ async function staged(answer, { changeBatch: batch, file }) {
325
359
  const refusal = doorRefusal(answer.body);
326
360
  if (refusal?.code !== 'files_unstaged')
327
361
  return null;
328
- const grants = refusal.fields.upload;
329
- if (!Array.isArray(grants))
362
+ const uploads = refusal.fields.upload;
363
+ if (!Array.isArray(uploads))
330
364
  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)
365
+ for (const named of uploads) {
366
+ const upload = houseUpload(named);
367
+ const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === named.sha256);
368
+ if (upload === null || change === undefined)
335
369
  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)
370
+ const finished = await finishedUpload(transport, upload, await file(change.localId));
371
+ if (finished?.body?.staged !== true)
343
372
  return false;
344
373
  }
345
374
  return true;
346
375
  }
347
- async function submit(batch, head, changes, delta, bytes, entry, handed, keep) {
376
+ async function submit(request, head, changes, delta, bytes, entry, handed, keep, into) {
348
377
  const inline = { ...head, changes, runtime: notifications.runtime };
349
378
  const prepared = uploadable(inline, bytes, declared())
350
379
  ? await uploaded(head, changes, delta, bytes, entry, handed, keep)
@@ -352,15 +381,20 @@ export function createAgentsHouseAdapter(options) {
352
381
  if ('outcome' in prepared)
353
382
  return prepared.outcome;
354
383
  const answer = await transport.door(DOOR_BATCH, prepared.body);
355
- const stagedFiles = await staged(answer, batch);
384
+ const stagedFiles = await staged(answer, request);
356
385
  if (stagedFiles === false)
357
386
  return UNCERTAIN;
358
387
  const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
359
- if (stagedFiles === true && (await staged(settled, batch)) !== null)
388
+ if (stagedFiles === true && (await staged(settled, request)) !== null)
360
389
  return UNCERTAIN;
361
- return mutationOutcome(settled.status, settled.body, entry.locator, entry.handle);
390
+ return stepped(mutationOutcome(settled.status, settled.body, entry.locator, entry.handle, into === undefined
391
+ ? undefined
392
+ : {
393
+ source: { authority: entry.authority, handle: entry.handle },
394
+ destination: { authority: into.authority, handle: into.handle },
395
+ }));
362
396
  }
363
- async function downloaded(answer, locator) {
397
+ function addressed(answer, locator) {
364
398
  const failure = classifyDoor(answer.status, answer.body, locator);
365
399
  if (failure !== null) {
366
400
  return failure.kind === 'scoped-refusal'
@@ -372,20 +406,56 @@ export function createAgentsHouseAdapter(options) {
372
406
  }
373
407
  : failure;
374
408
  }
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)
409
+ const address = parseFileAddress(answer.body);
410
+ return address === null ? UNCERTAIN_READ : { kind: 'address', ...address };
411
+ }
412
+ function linked(answer, locator) {
413
+ const address = addressed(answer, locator);
414
+ return address.kind === 'address'
415
+ ? { kind: 'link', url: address.url, name: address.name }
416
+ : address;
417
+ }
418
+ async function downloaded(ask, locator) {
419
+ let address = addressed(await ask(), locator);
420
+ if (address.kind !== 'address')
421
+ return address;
422
+ let read = await transport.download(address.url);
423
+ if (read.status === EXPIRED_ADDRESS) {
424
+ address = addressed(await ask(), locator);
425
+ if (address.kind !== 'address')
426
+ return address;
427
+ read = await transport.download(address.url);
428
+ }
429
+ if (read.status !== 200 || read.body === null)
380
430
  return UNCERTAIN_READ;
381
- return { kind: 'binary', blob: read.blob };
431
+ return {
432
+ kind: 'binary',
433
+ body: read.body,
434
+ mediaType: address.mediaType,
435
+ sha256: address.sha256,
436
+ };
437
+ }
438
+ function pathAddress(request) {
439
+ const entry = authorityFor(request.locator);
440
+ if (entry === undefined)
441
+ return null;
442
+ return () => transport.door(DOOR_FILES_GET, {
443
+ room_ref: entry.authority,
444
+ path: request.path,
445
+ revision: request.revision,
446
+ });
382
447
  }
383
448
  return {
449
+ roomTypes: HOUSE_ROOM_TYPES,
450
+ nameableRoomTypes: HOUSE_NAMEABLE_ROOM_TYPES,
384
451
  async authorize() {
385
452
  return options.authorize();
386
453
  },
387
- async discover(_authorization) {
388
- const answer = await transport.door(DOOR_DISCOVER, { prefixes: [...DISCOVER_PREFIXES] });
454
+ async discover(_authorization, scope) {
455
+ const answer = await transport.door(DOOR_DISCOVER, {
456
+ prefixes: [...DISCOVER_PREFIXES],
457
+ ...(scope === undefined ? {} : { rooms: scope.authorities }),
458
+ });
389
459
  const failed = discoveryFailure(answer.status, answer.body);
390
460
  if (failed !== null)
391
461
  return failed;
@@ -394,16 +464,31 @@ export function createAgentsHouseAdapter(options) {
394
464
  return { kind: 'transport-uncertainty' };
395
465
  bounds = document.bounds;
396
466
  const entries = trackedFrom(document);
397
- tracked.clear();
467
+ const covered = scope === undefined
468
+ ? null
469
+ : [
470
+ ...new Set([
471
+ ...[...tracked.values()]
472
+ .filter((entry) => scope.authorities.includes(entry.authority))
473
+ .map((entry) => entry.locator),
474
+ ...entries.map((entry) => entry.locator),
475
+ ]),
476
+ ];
477
+ if (covered === null)
478
+ tracked.clear();
479
+ else
480
+ for (const locator of covered)
481
+ tracked.delete(locator);
398
482
  for (const entry of entries)
399
483
  tracked.set(entry.locator, entry);
400
484
  for (const locator of handed.keys())
401
485
  if (!tracked.has(locator))
402
486
  handed.delete(locator);
403
- notifications.track(entries);
487
+ notifications.track([...tracked.values()]);
404
488
  return {
405
- ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document)),
489
+ ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions, [...tracked.values()])),
406
490
  member: document.member,
491
+ ...(covered === null ? {} : { covered }),
407
492
  };
408
493
  },
409
494
  async bootstrap(_authorization, request) {
@@ -412,7 +497,7 @@ export function createAgentsHouseAdapter(options) {
412
497
  return { kind: 'room-not-readable', locator: request.locator };
413
498
  const asked = { entry, held: request.held };
414
499
  if (request.pageToken !== undefined) {
415
- return continuedBootstrap(asked, JSON.parse(request.pageToken));
500
+ return partPage(asked, JSON.parse(request.pageToken), request.received, request.arriving);
416
501
  }
417
502
  return bootstrapTogether(asked);
418
503
  },
@@ -434,18 +519,27 @@ export function createAgentsHouseAdapter(options) {
434
519
  const entry = authorityFor(request.locator);
435
520
  if (entry === undefined)
436
521
  return { kind: 'room-not-readable', locator: request.locator };
437
- const mapped = houseChanges(request.changeBatch, entry.handle);
522
+ const batch = request.changeBatch;
523
+ const destination = batch.between?.destination.room;
524
+ const into = destination === undefined ? undefined : authorityFor(destination);
525
+ if (destination !== undefined && into === undefined) {
526
+ return { kind: 'room-not-readable', locator: destination };
527
+ }
528
+ const mapped = into === undefined
529
+ ? houseChanges(batch, entry.handle)
530
+ : houseMove(batch, entry.handle, into.handle);
438
531
  if (mapped.kind === 'unmapped')
439
532
  return rejectedMapping(mapped);
440
- const head = operationHead(request.changeBatch);
533
+ const head = operationHead(batch);
441
534
  const delta = deltaBody(mapped.changes);
442
535
  const bytes = deltaBytes(delta);
443
536
  const handed = request.upload;
444
- const batch = request.changeBatch;
445
- const outcome = await submit(batch, head, mapped.changes, delta, bytes, entry, handed, keep);
537
+ const answer = (upload) => submit(request, head, mapped.changes, delta, bytes, entry, upload, keep, into);
538
+ const settle = (outcome) => into === undefined ? adopted(entry, outcome) : adoptedMove(entry, into, outcome);
539
+ const outcome = await answer(handed);
446
540
  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));
541
+ return settle(outcome);
542
+ return settle(await answer(undefined));
449
543
  },
450
544
  async history(_authorization, request) {
451
545
  const entry = authorityFor(request.locator);
@@ -463,35 +557,16 @@ export function createAgentsHouseAdapter(options) {
463
557
  });
464
558
  },
465
559
  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);
560
+ const ask = pathAddress(request);
561
+ if (ask === null)
562
+ return { kind: 'room-not-readable', locator: request.locator };
563
+ return downloaded(ask, request.locator);
486
564
  },
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 }) };
565
+ async link(_authorization, request) {
566
+ const ask = pathAddress(request);
567
+ if (ask === null)
568
+ return { kind: 'room-not-readable', locator: request.locator };
569
+ return linked(await ask(), request.locator);
495
570
  },
496
571
  reached(locator, version, target) {
497
572
  return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
@@ -505,7 +580,10 @@ export function createAgentsHouseAdapter(options) {
505
580
  roomStream: {
506
581
  hold: held,
507
582
  reopen: notifications.reopen,
508
- observe: notifications.observe,
583
+ observe(notify, receipts, revisions) {
584
+ heldRevisions = revisions;
585
+ return notifications.observe(notify, receipts);
586
+ },
509
587
  published(locator, roomVersion) {
510
588
  const at = logPosition(roomVersion);
511
589
  if (at !== null)
@@ -0,0 +1,2 @@
1
+ export declare const APP_REFERENCE_PATTERN: RegExp;
2
+ //# sourceMappingURL=app-reference.d.ts.map