@agentshouse/mdruntime-adapter 0.1.0-cd61dfca5d699 → 0.1.0-cd8cfa169a55d

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 +4 -22
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +20 -134
  5. package/dist/adapter.d.ts.map +1 -1
  6. package/dist/adapter.js +255 -163
  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 +34 -29
  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 +6 -1
  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 +57 -26
  32. package/dist/read.d.ts.map +1 -1
  33. package/dist/read.js +268 -152
  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 +81 -104
  39. package/dist/stream.d.ts +3 -3
  40. package/dist/stream.d.ts.map +1 -1
  41. package/dist/stream.js +38 -35
  42. package/dist/transport.d.ts +11 -10
  43. package/dist/transport.d.ts.map +1 -1
  44. package/dist/transport.js +2 -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 +92 -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,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 { 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
- 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,83 @@ export function createAgentsHouseAdapter(options) {
51
54
  const notifications = houseNotifications(transport, 'credential');
52
55
  const tracked = new Map();
53
56
  const handed = new Map();
57
+ let heldRevisions;
54
58
  let bounds;
55
59
  function declared() {
56
60
  if (bounds === undefined)
57
61
  throw new Error('no Discovery declared the bounds');
58
62
  return bounds;
59
63
  }
60
- async function bootstrappedRoom(envelope, sent, asked, continued) {
64
+ async function fetchedPart(entry, continued, listed, received, arriving) {
65
+ if (received !== undefined) {
66
+ const kept = new Uint8Array(received);
67
+ const held = await housePart(continued.room, kept, listed, 'credential');
68
+ if (held !== null)
69
+ return 'kind' in held ? held : { scope: held, frame: kept };
70
+ }
71
+ for (let attempt = 0; attempt < PART_ATTEMPTS; attempt += 1) {
72
+ const answered = await transport.door(DOOR_BOOTSTRAP, {
73
+ prefixes: [authorityRoot(entry.handle)],
74
+ part: listed.part,
75
+ ...(continued.held === undefined ? {} : { held: [continued.held] }),
76
+ }, partArrival(listed, arriving));
77
+ const failure = classifyRead(answered.status, answered.body, entry.locator);
78
+ if (failure !== null)
79
+ return failure;
80
+ const frame = partFrame(answered.body);
81
+ if (frame === null)
82
+ continue;
83
+ const scope = await housePart(continued.room, frame, listed, 'credential');
84
+ if (scope !== null)
85
+ return 'kind' in scope ? scope : { scope, frame };
86
+ }
87
+ return unverifiedPart(entry.locator);
88
+ }
89
+ async function manifestPage(asked, continued) {
90
+ const inventory = partInventory(continued.room);
91
+ if (inventory.parts === 0) {
92
+ const page = await partPage(asked, continued, undefined, undefined);
93
+ return page.kind === 'bootstrap' ? { ...page, inventory } : page;
94
+ }
95
+ const { models, seen } = await manifestedModels(continued.room, asked.entry);
96
+ return manifested(continued.room, inventory, JSON.stringify({ ...continued, seen }), models);
97
+ }
98
+ async function partPage(asked, continued, received, arriving) {
61
99
  const { entry } = asked;
62
- if (sent === undefined)
63
- return { kind: 'room-not-readable', locator: entry.locator };
64
- const scope = await reused(sent, asked.held);
100
+ const listed = continued.room.parts[continued.next];
101
+ const sent = listed === undefined
102
+ ? { scope: emptyScope(continued.room), frame: null }
103
+ : await fetchedPart(entry, continued, listed, received, arriving);
104
+ if ('kind' in sent)
105
+ return sent;
106
+ const scope = await reused(sent.scope, asked.held);
65
107
  if (scope === null) {
66
108
  return asked.held === undefined
67
109
  ? UNCERTAIN_READ
68
- : continued === null
69
- ? bootstrapTogether({ entry, held: undefined })
70
- : continuedBootstrap({ entry, held: undefined }, continued);
110
+ : bootstrapTogether({ entry, held: undefined });
71
111
  }
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));
112
+ const next = continued.next + 1;
113
+ const more = next < continued.room.parts.length;
114
+ if (continued.next === 0 && !more)
115
+ handed.set(entry.locator, roomVersion(scope));
76
116
  else
77
117
  handed.delete(entry.locator);
78
118
  notifications.reach(entry.authority, scope);
79
119
  const seen = [
80
- ...(continued?.seen ?? []),
81
- ...entry.schemas.filter((schema) => scope.files.some((file) => file.path === schema)),
120
+ ...continued.seen,
121
+ ...(scope.files.some((file) => file.path === entry.schema) ? [entry.schema] : []),
82
122
  ];
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 }) };
123
+ const models = await partModels(scope, entry, new Set(seen), !more);
87
124
  return {
88
- ...bootstrapOf(scope),
125
+ ...(await stepped(bootstrapping(scope))),
89
126
  ...(models === undefined ? {} : { models }),
90
- ...(continuation === null ? {} : { pageToken: JSON.stringify(continuation) }),
127
+ ...(more ? { pageToken: JSON.stringify({ ...continued, next, seen }) } : {}),
128
+ ...(sent.frame === null ? {} : { received: sent.frame }),
91
129
  };
92
130
  }
93
131
  async function enumeratedPage(ask, page) {
94
132
  const answer = await transport.door(DOOR_ENUMERATE, {
95
- prefixes: contentPrefixes(ask.entry),
133
+ prefixes: [authorityRoot(ask.entry.handle)],
96
134
  positions: [
97
135
  {
98
136
  ...presented(ask.entry.authority, { position: page.after, logEpoch: ask.from.logEpoch }),
@@ -116,21 +154,24 @@ export function createAgentsHouseAdapter(options) {
116
154
  if ('kind' in view)
117
155
  return view;
118
156
  notifications.reach(ask.entry.authority, view);
119
- const answer = refreshFrom(view, ask.fromRoomVersion);
157
+ const answer = await refreshFrom(view, ask.fromRoomVersion);
120
158
  if (answer.kind !== 'net-advancement')
121
159
  return answer;
122
- handed.set(ask.entry.locator, enumeratedHanded(view, handed.get(ask.entry.locator)));
160
+ handed.set(ask.entry.locator, roomVersion(view));
123
161
  const models = await viewModels(view, ask.entry);
124
162
  return models === undefined ? answer : { ...answer, models };
125
163
  }
126
164
  async function streamed(entry, request, from) {
127
- const held = handed.get(entry.locator);
128
- if (held?.roomVersion !== request.fromRoomVersion)
165
+ if (handed.get(entry.locator) !== request.fromRoomVersion)
129
166
  return null;
130
- const advanced = notifications.advance(entry, from, (path, revision) => held.revisions.get(path) === revision);
167
+ const asked = notifications.edited(entry, from);
168
+ const kept = asked.length === 0 || heldRevisions === undefined
169
+ ? new Map()
170
+ : await heldRevisions(entry.locator, asked);
171
+ const advanced = await stepped(notifications.advance(entry, from, (path, revision) => kept.get(path) === revision));
131
172
  if (advanced === null)
132
173
  return null;
133
- handed.set(entry.locator, streamedHanded(held, advanced));
174
+ handed.set(entry.locator, advanced.page.roomVersion);
134
175
  const models = await streamedModels(advanced.schemas, entry, 'credential');
135
176
  return models === undefined ? advanced.page : { ...advanced.page, models };
136
177
  }
@@ -140,20 +181,27 @@ export function createAgentsHouseAdapter(options) {
140
181
  const at = logPosition(room.roomVersion);
141
182
  if (entry === undefined || at === null)
142
183
  return [];
143
- handed.set(room.locator, heldHanded(room));
184
+ handed.set(room.locator, room.roomVersion);
144
185
  notifications.reach(entry.authority, at);
145
186
  return [presented(entry.authority, at)];
146
187
  });
147
188
  notifications.send(kept);
148
189
  }
149
- function adopted(entry, outcome) {
190
+ async function adoptedMove(entry, into, outcome) {
191
+ if (outcome.kind === 'accepted' && outcome.destination !== undefined) {
192
+ await adopted(into, { kind: 'accepted', ...outcome.destination });
193
+ }
194
+ return adopted(entry, outcome);
195
+ }
196
+ async function adopted(entry, outcome) {
150
197
  if (outcome.kind !== 'accepted')
151
198
  return outcome;
152
199
  const held = handed.get(entry.locator);
153
200
  if (held === undefined)
154
201
  return outcome;
155
- const follows = followsVersion(held.roomVersion, outcome.roomVersion);
156
- handed.set(entry.locator, acceptedHanded(held, outcome, follows ? outcome.roomVersion : held.roomVersion));
202
+ const follows = followsVersion(held, outcome.roomVersion);
203
+ if (follows)
204
+ handed.set(entry.locator, outcome.roomVersion);
157
205
  const at = logPosition(outcome.roomVersion);
158
206
  if (follows && at !== null)
159
207
  notifications.reach(entry.authority, at);
@@ -176,68 +224,61 @@ export function createAgentsHouseAdapter(options) {
176
224
  return result;
177
225
  return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
178
226
  }
179
- function weight(entry) {
180
- return contentPrefixes(entry).length;
181
- }
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) ?? []);
227
+ async function holding(asks) {
228
+ const bound = Math.floor(declared().max_held_files / Math.max(1, asks.filter((ask) => ask.held !== undefined).length));
229
+ const held = [];
230
+ for (const ask of asks) {
231
+ if (ask.held === undefined || bound === 0)
232
+ continue;
233
+ const files = await heldFiles(ask.entry.authority, ask.held, bound);
234
+ if (files !== null)
235
+ held.push(files);
236
+ }
237
+ return held;
185
238
  }
186
239
  async function bootstrapped(asks, answer) {
187
240
  let open = asks.map((asked, ask) => ({ entry: asked.entry, asked, ask }));
188
241
  while (open.length > 0) {
189
242
  const unreached = [];
190
- for (const part of selections(open, ({ entry }) => weight(entry), declared().max_prefixes)) {
191
- const held = holding(part.map(({ asked }) => asked), null);
243
+ for (const part of selections(open, declared().max_prefixes)) {
244
+ const held = await holding(part.map(({ asked }) => asked));
192
245
  const answered = await transport.door(DOOR_BOOTSTRAP, {
193
- prefixes: part.flatMap(({ entry }) => contentPrefixes(entry)),
246
+ prefixes: part.map(({ entry }) => authorityRoot(entry.handle)),
194
247
  ...(held.length === 0 ? {} : { held }),
195
248
  });
196
249
  const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
197
- const envelope = classified[0] === null ? parseEnvelope(answered.body) : null;
250
+ const parted = classified[0] === null ? parseParted(answered.body) : null;
198
251
  for (const [index, asked] of part.entries()) {
199
252
  const failure = classified[index];
200
253
  if (failure !== null && failure !== undefined) {
201
254
  answer(asked.ask, failure);
202
255
  continue;
203
256
  }
204
- if (envelope === null) {
257
+ if (parted === null) {
205
258
  answer(asked.ask, UNCERTAIN_READ);
206
259
  continue;
207
260
  }
208
- const scope = envelope.scopes.find((candidate) => matchesScope(candidate, asked.entry));
209
- if (scope === undefined && envelope.next !== undefined) {
261
+ const room = parted.rooms.find((candidate) => candidate.anchor === asked.entry.authority);
262
+ if (room === undefined && parted.next !== undefined) {
210
263
  unreached.push(asked);
211
264
  continue;
212
265
  }
213
- answer(asked.ask, await bootstrappedRoom(envelope, scope, asked.asked, null));
266
+ if (room === undefined) {
267
+ answer(asked.ask, { kind: 'room-not-readable', locator: asked.entry.locator });
268
+ continue;
269
+ }
270
+ const kept = held.find((files) => files.room === asked.entry.authority);
271
+ answer(asked.ask, await manifestPage(asked.asked, {
272
+ room,
273
+ next: 0,
274
+ seen: [],
275
+ ...(kept === undefined ? {} : { held: kept }),
276
+ }));
214
277
  }
215
278
  }
216
279
  open = unreached;
217
280
  }
218
281
  }
219
- async function continuedBootstrap(asked, continued) {
220
- const { entry } = asked;
221
- const after = {
222
- room: entry.authority,
223
- path: continued.after,
224
- ...(continued.cut === undefined ? {} : { cut: continued.cut }),
225
- };
226
- const held = holding([asked], continued.after);
227
- const answered = await transport.door(DOOR_BOOTSTRAP, {
228
- prefixes: contentPrefixes(entry),
229
- after,
230
- ...(held.length === 0 ? {} : { held }),
231
- });
232
- const failure = classifyRead(answered.status, answered.body, entry.locator);
233
- if (failure !== null)
234
- return failure;
235
- const envelope = parseEnvelope(answered.body);
236
- if (envelope === null)
237
- return UNCERTAIN_READ;
238
- const scope = envelope.scopes.find((candidate) => matchesScope(candidate, entry));
239
- return bootstrappedRoom(envelope, scope, asked, continued);
240
- }
241
282
  function layered(asks) {
242
283
  const depths = new Map();
243
284
  const layers = [];
@@ -250,12 +291,12 @@ export function createAgentsHouseAdapter(options) {
250
291
  }
251
292
  async function enumerated(asks) {
252
293
  const answers = [];
253
- const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, ({ ask }) => weight(ask.entry), declared().max_prefixes));
294
+ const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, declared().max_prefixes));
254
295
  for (const part of parts) {
255
296
  let open = [...part];
256
297
  while (open.length > 0) {
257
298
  const answer = await transport.door(DOOR_ENUMERATE, {
258
- prefixes: open.flatMap(({ ask }) => contentPrefixes(ask.entry)),
299
+ prefixes: open.map(({ ask }) => authorityRoot(ask.entry.handle)),
259
300
  positions: open.map(({ ask }) => presented(ask.entry.authority, ask.from)),
260
301
  });
261
302
  const classified = open.map(({ ask }) => classifyRead(answer.status, answer.body, ask.entry.locator));
@@ -294,10 +335,12 @@ export function createAgentsHouseAdapter(options) {
294
335
  }
295
336
  const minted = await transport.door(DOOR_BATCH_UPLOAD, { ...head, paths, bytes, sha256 });
296
337
  if (minted.status !== 200) {
297
- return { outcome: mutationOutcome(minted.status, minted.body, entry.locator, entry.handle) };
338
+ return {
339
+ outcome: await stepped(mutationOutcome(minted.status, minted.body, entry.locator, entry.handle)),
340
+ };
298
341
  }
299
342
  const grant = parseGrant(minted.body);
300
- if (grant === null || grant.method !== 'POST')
343
+ if (grant === null)
301
344
  return { outcome: UNCERTAIN };
302
345
  const delivered = await transport.transfer({
303
346
  act: 'authored_batch_upload',
@@ -307,36 +350,31 @@ export function createAgentsHouseAdapter(options) {
307
350
  if (delivered.status !== 200)
308
351
  return { outcome: UNCERTAIN };
309
352
  const holding = parseGrant(delivered.body);
310
- if (holding === null || holding.method !== 'POST')
353
+ if (holding === null)
311
354
  return { outcome: UNCERTAIN };
312
355
  const upload = { url: holding.url, operation: holding.operation, bytes, sha256 };
313
356
  await keep(upload);
314
357
  return { body: { ...head, paths, upload, runtime: notifications.runtime } };
315
358
  }
316
- async function staged(answer, batch) {
359
+ async function staged(answer, { changeBatch: batch, file }) {
317
360
  const refusal = doorRefusal(answer.body);
318
361
  if (refusal?.code !== 'files_unstaged')
319
362
  return null;
320
- const grants = refusal.fields.upload;
321
- if (!Array.isArray(grants))
363
+ const uploads = refusal.fields.upload;
364
+ if (!Array.isArray(uploads))
322
365
  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)
366
+ for (const named of uploads) {
367
+ const upload = houseUpload(named);
368
+ const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === named.sha256);
369
+ if (upload === null || change === undefined)
327
370
  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)
371
+ const finished = await finishedUpload(transport, upload, await file(change.localId));
372
+ if (finished?.body?.staged !== true)
335
373
  return false;
336
374
  }
337
375
  return true;
338
376
  }
339
- async function submit(batch, head, changes, delta, bytes, entry, handed, keep) {
377
+ async function submit(request, head, changes, delta, bytes, entry, handed, keep, into) {
340
378
  const inline = { ...head, changes, runtime: notifications.runtime };
341
379
  const prepared = uploadable(inline, bytes, declared())
342
380
  ? await uploaded(head, changes, delta, bytes, entry, handed, keep)
@@ -344,15 +382,20 @@ export function createAgentsHouseAdapter(options) {
344
382
  if ('outcome' in prepared)
345
383
  return prepared.outcome;
346
384
  const answer = await transport.door(DOOR_BATCH, prepared.body);
347
- const stagedFiles = await staged(answer, batch);
385
+ const stagedFiles = await staged(answer, request);
348
386
  if (stagedFiles === false)
349
387
  return UNCERTAIN;
350
388
  const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
351
- if (stagedFiles === true && (await staged(settled, batch)) !== null)
389
+ if (stagedFiles === true && (await staged(settled, request)) !== null)
352
390
  return UNCERTAIN;
353
- return mutationOutcome(settled.status, settled.body, entry.locator, entry.handle);
391
+ return stepped(mutationOutcome(settled.status, settled.body, entry.locator, entry.handle, into === undefined
392
+ ? undefined
393
+ : {
394
+ source: { authority: entry.authority, handle: entry.handle },
395
+ destination: { authority: into.authority, handle: into.handle },
396
+ }));
354
397
  }
355
- function granted(answer, locator) {
398
+ function addressed(answer, locator) {
356
399
  const failure = classifyDoor(answer.status, answer.body, locator);
357
400
  if (failure !== null) {
358
401
  return failure.kind === 'scoped-refusal'
@@ -364,33 +407,61 @@ export function createAgentsHouseAdapter(options) {
364
407
  }
365
408
  : failure;
366
409
  }
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 };
410
+ const address = parseFileAddress(answer.body);
411
+ return address === null ? UNCERTAIN_READ : { kind: 'address', ...address };
412
+ }
413
+ function linked(answer, locator) {
414
+ const address = addressed(answer, locator);
415
+ return address.kind === 'address'
416
+ ? { kind: 'link', url: address.url, name: address.name, mediaType: address.mediaType }
417
+ : address;
418
+ }
419
+ function refusedAttach(answer, locator) {
420
+ const failure = classifyDoor(answer.status, answer.body, locator);
421
+ if (failure === null)
422
+ return null;
423
+ return failure.kind === 'scoped-refusal'
424
+ ? {
425
+ kind: 'rejected',
426
+ code: failure.code,
427
+ fields: failure.fields,
428
+ retryable: failure.retryable,
429
+ }
430
+ : failure;
373
431
  }
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)
432
+ async function downloaded(ask, locator) {
433
+ let address = addressed(await ask(), locator);
434
+ if (address.kind !== 'address')
435
+ return address;
436
+ let read = await transport.download(address.url);
437
+ if (read.status === EXPIRED_ADDRESS) {
438
+ address = addressed(await ask(), locator);
439
+ if (address.kind !== 'address')
440
+ return address;
441
+ read = await transport.download(address.url);
442
+ }
443
+ if (read.status !== 200 || read.body === null)
380
444
  return UNCERTAIN_READ;
381
- return { kind: 'binary', blob: read.blob };
445
+ return {
446
+ kind: 'binary',
447
+ body: read.body,
448
+ mediaType: address.mediaType,
449
+ sha256: address.sha256,
450
+ };
382
451
  }
383
- function pathGrant(request) {
452
+ function pathAddress(request) {
384
453
  const entry = authorityFor(request.locator);
385
454
  if (entry === undefined)
386
455
  return null;
387
- return transport.door(DOOR_FILES_GET, {
456
+ return () => transport.door(DOOR_FILES_GET, {
388
457
  room_ref: entry.authority,
389
458
  path: request.path,
390
459
  revision: request.revision,
391
460
  });
392
461
  }
393
462
  return {
463
+ roomTypes: HOUSE_ROOM_TYPES,
464
+ nameableRoomTypes: HOUSE_NAMEABLE_ROOM_TYPES,
394
465
  async authorize() {
395
466
  return options.authorize();
396
467
  },
@@ -440,7 +511,7 @@ export function createAgentsHouseAdapter(options) {
440
511
  return { kind: 'room-not-readable', locator: request.locator };
441
512
  const asked = { entry, held: request.held };
442
513
  if (request.pageToken !== undefined) {
443
- return continuedBootstrap(asked, JSON.parse(request.pageToken));
514
+ return partPage(asked, JSON.parse(request.pageToken), request.received, request.arriving);
444
515
  }
445
516
  return bootstrapTogether(asked);
446
517
  },
@@ -462,18 +533,27 @@ export function createAgentsHouseAdapter(options) {
462
533
  const entry = authorityFor(request.locator);
463
534
  if (entry === undefined)
464
535
  return { kind: 'room-not-readable', locator: request.locator };
465
- const mapped = houseChanges(request.changeBatch, entry.handle);
536
+ const batch = request.changeBatch;
537
+ const destination = batch.between?.destination.room;
538
+ const into = destination === undefined ? undefined : authorityFor(destination);
539
+ if (destination !== undefined && into === undefined) {
540
+ return { kind: 'room-not-readable', locator: destination };
541
+ }
542
+ const mapped = into === undefined
543
+ ? houseChanges(batch, entry.handle)
544
+ : houseMove(batch, entry.handle, into.handle);
466
545
  if (mapped.kind === 'unmapped')
467
546
  return rejectedMapping(mapped);
468
- const head = operationHead(request.changeBatch);
547
+ const head = operationHead(batch);
469
548
  const delta = deltaBody(mapped.changes);
470
549
  const bytes = deltaBytes(delta);
471
550
  const handed = request.upload;
472
- const batch = request.changeBatch;
473
- const outcome = await submit(batch, head, mapped.changes, delta, bytes, entry, handed, keep);
551
+ const answer = (upload) => submit(request, head, mapped.changes, delta, bytes, entry, upload, keep, into);
552
+ const settle = (outcome) => into === undefined ? adopted(entry, outcome) : adoptedMove(entry, into, outcome);
553
+ const outcome = await answer(handed);
474
554
  if (handed === undefined || !lapsedGrant(outcome))
475
- return adopted(entry, outcome);
476
- return adopted(entry, await submit(batch, head, mapped.changes, delta, bytes, entry, undefined, keep));
555
+ return settle(outcome);
556
+ return settle(await answer(undefined));
477
557
  },
478
558
  async history(_authorization, request) {
479
559
  const entry = authorityFor(request.locator);
@@ -491,36 +571,45 @@ export function createAgentsHouseAdapter(options) {
491
571
  });
492
572
  },
493
573
  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);
574
+ const ask = pathAddress(request);
575
+ if (ask === null)
576
+ return { kind: 'room-not-readable', locator: request.locator };
577
+ return downloaded(ask, request.locator);
509
578
  },
510
579
  async link(_authorization, request) {
511
- const asked = pathGrant(request);
512
- if (asked === null)
580
+ const ask = pathAddress(request);
581
+ if (ask === null)
513
582
  return { kind: 'room-not-readable', locator: request.locator };
514
- return granted(await asked, request.locator);
583
+ return linked(await ask(), request.locator);
515
584
  },
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 }) };
585
+ async attach(_authorization, request) {
586
+ const entry = authorityFor(request.locator);
587
+ if (entry === undefined)
588
+ return { kind: 'room-not-readable', locator: request.locator };
589
+ const declared = await transport.door(DOOR_FILES_ATTACH, {
590
+ room_ref: entry.authority,
591
+ name: request.name,
592
+ bytes: request.byteLength,
593
+ sha256: request.sha256,
594
+ media_type: request.mediaType.length > 0 ? request.mediaType : OCTET_STREAM,
595
+ });
596
+ const refused = refusedAttach(declared, request.locator);
597
+ if (refused !== null)
598
+ return refused;
599
+ const upload = houseUpload(declared.body?.upload);
600
+ if (upload === null)
601
+ return UNCERTAIN_READ;
602
+ const finished = await finishedUpload(transport, upload, request.blob);
603
+ if (finished === null)
604
+ return UNCERTAIN_READ;
605
+ const unfinished = refusedAttach(finished, request.locator);
606
+ if (unfinished !== null)
607
+ return unfinished;
608
+ const { path, revision } = (finished.body ?? {});
609
+ const root = `/rooms/${entry.handle}/`;
610
+ return typeof path === 'string' && path.startsWith(root) && typeof revision === 'string'
611
+ ? { kind: 'attached', path: path.slice(root.length), revision }
612
+ : UNCERTAIN_READ;
524
613
  },
525
614
  reached(locator, version, target) {
526
615
  return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
@@ -534,7 +623,10 @@ export function createAgentsHouseAdapter(options) {
534
623
  roomStream: {
535
624
  hold: held,
536
625
  reopen: notifications.reopen,
537
- observe: notifications.observe,
626
+ observe(notify, receipts, revisions) {
627
+ heldRevisions = revisions;
628
+ return notifications.observe(notify, receipts);
629
+ },
538
630
  published(locator, roomVersion) {
539
631
  const at = logPosition(roomVersion);
540
632
  if (at !== null)
@@ -0,0 +1,2 @@
1
+ export declare const APP_REFERENCE_PATTERN: RegExp;
2
+ //# sourceMappingURL=app-reference.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-reference.d.ts","sourceRoot":"","sources":["../src/app-reference.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,qBAAqB,QAEjC,CAAC"}