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

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 (62) hide show
  1. package/README.md +2 -2
  2. package/dist/actions.d.ts +21 -5
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +100 -113
  5. package/dist/adapter.d.ts +1 -0
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/adapter.js +346 -100
  8. package/dist/app-reference.d.ts +2 -0
  9. package/dist/app-reference.d.ts.map +1 -0
  10. package/dist/app-reference.js +2 -0
  11. package/dist/crockford.d.ts +4 -0
  12. package/dist/crockford.d.ts.map +1 -0
  13. package/dist/crockford.js +3 -0
  14. package/dist/discovery.d.ts +7 -10
  15. package/dist/discovery.d.ts.map +1 -1
  16. package/dist/discovery.js +43 -34
  17. package/dist/gather.d.ts +1 -1
  18. package/dist/gather.d.ts.map +1 -1
  19. package/dist/gather.js +4 -14
  20. package/dist/house-model.d.ts +2 -0
  21. package/dist/house-model.d.ts.map +1 -0
  22. package/dist/house-model.js +443 -0
  23. package/dist/index.d.ts +7 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +6 -1
  26. package/dist/model.d.ts +5 -2
  27. package/dist/model.d.ts.map +1 -1
  28. package/dist/model.js +10 -15
  29. package/dist/part.d.ts +15 -0
  30. package/dist/part.d.ts.map +1 -0
  31. package/dist/part.js +82 -0
  32. package/dist/read.d.ts +77 -23
  33. package/dist/read.d.ts.map +1 -1
  34. package/dist/read.js +358 -144
  35. package/dist/refusal.d.ts.map +1 -1
  36. package/dist/refusal.js +2 -1
  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 +124 -0
  40. package/dist/site.d.ts +1 -1
  41. package/dist/site.d.ts.map +1 -1
  42. package/dist/site.js +160 -59
  43. package/dist/stream.d.ts +5 -4
  44. package/dist/stream.d.ts.map +1 -1
  45. package/dist/stream.js +356 -43
  46. package/dist/transport.d.ts +67 -10
  47. package/dist/transport.d.ts.map +1 -1
  48. package/dist/transport.js +3 -0
  49. package/dist/upload.d.ts +12 -0
  50. package/dist/upload.d.ts.map +1 -0
  51. package/dist/upload.js +63 -0
  52. package/dist/version.d.ts +1 -0
  53. package/dist/version.d.ts.map +1 -1
  54. package/dist/version.js +1 -1
  55. package/dist/write.d.ts +13 -9
  56. package/dist/write.d.ts.map +1 -1
  57. package/dist/write.js +130 -40
  58. package/package.json +8 -5
  59. package/AGENTS.md +0 -290
  60. package/dist/links.d.ts +0 -20
  61. package/dist/links.d.ts.map +0 -1
  62. package/dist/links.js +0 -104
package/dist/adapter.js CHANGED
@@ -1,77 +1,176 @@
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, } from './read.js';
6
- import { acceptedLinks, enumeratedLinks, heldLinks, scopeLinks, streamedLinks, } from './links.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';
8
+ import { sha256Hex } from './digest.js';
9
+ import { finishedUpload, houseUpload } from './upload.js';
7
10
  import { houseAction } from './actions.js';
8
11
  import { classifyDoor, classifyRead, discoveryFailure, doorRefusal } from './refusal.js';
9
12
  import { houseNotifications } from './stream.js';
10
- import { DOOR_BATCH, DOOR_BATCH_UPLOAD, DOOR_BOOTSTRAP, DOOR_DISCOVER, DOOR_ENUMERATE, DOOR_REVISIONS, } from './transport.js';
11
- import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
13
+ import { DOOR_BATCH, DOOR_BATCH_UPLOAD, DOOR_BOOTSTRAP, DOOR_DISCOVER, DOOR_ENUMERATE, 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';
12
16
  import { followsVersion, logPosition, presented, reachedVersion, roomVersion, } from './version.js';
13
17
  const UNCERTAIN = { kind: 'transport-uncertainty' };
14
18
  const UNCERTAIN_READ = { kind: 'transport-uncertainty' };
19
+ const EXPIRED_ADDRESS = 403;
15
20
  function lapsedGrant(outcome) {
16
21
  return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
17
22
  }
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 };
28
+ }
29
+ async function reused(scope, held) {
30
+ const sent = new Set(scope.contents.map((entry) => entry.path));
31
+ const contents = [...scope.contents];
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) {
43
+ return null;
44
+ }
45
+ contents.push({ path: file.path, content: found.content });
46
+ }
47
+ return { ...scope, contents };
48
+ }
18
49
  export function createAgentsHouseAdapter(options) {
19
50
  if ('site' in options)
20
- return createSiteAdapter(options.site);
51
+ return createSiteAdapter(options.site, options.snapshot === true);
21
52
  const transport = options.transport;
22
53
  const notifications = houseNotifications(transport, 'credential');
23
54
  const tracked = new Map();
24
55
  const handed = new Map();
56
+ let heldRevisions;
25
57
  let bounds;
26
58
  function declared() {
27
59
  if (bounds === undefined)
28
60
  throw new Error('no Discovery declared the bounds');
29
61
  return bounds;
30
62
  }
31
- async function bootstrappedRoom(envelope, scope, entry, continued) {
32
- if (scope === undefined)
33
- return { kind: 'room-not-readable', locator: entry.locator };
34
- const next = envelope.next?.room === entry.authority ? envelope.next.path : null;
35
- if (continued === null && next === null)
36
- handed.set(entry.locator, scopeLinks(scope));
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) {
98
+ const { entry } = asked;
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);
106
+ if (scope === null) {
107
+ return asked.held === undefined
108
+ ? UNCERTAIN_READ
109
+ : bootstrapTogether({ entry, held: undefined });
110
+ }
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));
37
115
  else
38
116
  handed.delete(entry.locator);
39
117
  notifications.reach(entry.authority, scope);
40
118
  const seen = [
41
- ...(continued?.seen ?? []),
42
- ...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] : []),
43
121
  ];
44
- const models = await partModels(scope, entry, new Set(seen), next === null);
45
- const continuation = next === null ? null : { after: next, seen };
122
+ const models = await partModels(scope, entry, new Set(seen), !more);
46
123
  return {
47
- ...bootstrapOf(scope),
124
+ ...(await stepped(bootstrapping(scope))),
48
125
  ...(models === undefined ? {} : { models }),
49
- ...(continuation === null ? {} : { pageToken: JSON.stringify(continuation) }),
126
+ ...(more ? { pageToken: JSON.stringify({ ...continued, next, seen }) } : {}),
127
+ ...(sent.frame === null ? {} : { received: sent.frame }),
50
128
  };
51
129
  }
130
+ async function enumeratedPage(ask, page) {
131
+ const answer = await transport.door(DOOR_ENUMERATE, {
132
+ prefixes: [authorityRoot(ask.entry.handle)],
133
+ positions: [
134
+ {
135
+ ...presented(ask.entry.authority, { position: page.after, logEpoch: ask.from.logEpoch }),
136
+ page: { through: page.through, read: page.read },
137
+ },
138
+ ],
139
+ });
140
+ const failure = classifyRead(answer.status, answer.body, ask.entry.locator);
141
+ if (failure !== null)
142
+ return failure;
143
+ const enumeration = parseEnumeration(answer.body, 'credential');
144
+ if (enumeration === null)
145
+ return UNCERTAIN_READ;
146
+ return (viewOf(enumeration, ask.entry) ?? { kind: 'room-not-readable', locator: ask.entry.locator });
147
+ }
52
148
  async function enumeratedRoom(enumeration, ask) {
53
- const view = viewOf(enumeration, ask.entry);
54
- if (view === undefined)
149
+ const first = viewOf(enumeration, ask.entry);
150
+ if (first === undefined)
55
151
  return { kind: 'room-not-readable', locator: ask.entry.locator };
152
+ const view = await wholeView(first, (page) => enumeratedPage(ask, page));
153
+ if ('kind' in view)
154
+ return view;
56
155
  notifications.reach(ask.entry.authority, view);
57
- const answer = refreshFrom(view, ask.fromRoomVersion);
156
+ const answer = await refreshFrom(view, ask.fromRoomVersion);
58
157
  if (answer.kind !== 'net-advancement')
59
158
  return answer;
60
- handed.set(ask.entry.locator, enumeratedLinks(view, handed.get(ask.entry.locator)));
159
+ handed.set(ask.entry.locator, roomVersion(view));
61
160
  const models = await viewModels(view, ask.entry);
62
161
  return models === undefined ? answer : { ...answer, models };
63
162
  }
64
163
  async function streamed(entry, request, from) {
65
- const links = handed.get(entry.locator);
66
- if (links?.roomVersion !== request.fromRoomVersion)
164
+ if (handed.get(entry.locator) !== request.fromRoomVersion)
67
165
  return null;
68
- const advanced = notifications.advance(entry, from, (path, revision) => links.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));
69
171
  if (advanced === null)
70
172
  return null;
71
- const relinked = streamedLinks(links, advanced);
72
- if (relinked === null)
73
- return null;
74
- handed.set(entry.locator, relinked);
173
+ handed.set(entry.locator, advanced.page.roomVersion);
75
174
  const models = await streamedModels(advanced.schemas, entry, 'credential');
76
175
  return models === undefined ? advanced.page : { ...advanced.page, models };
77
176
  }
@@ -81,28 +180,31 @@ export function createAgentsHouseAdapter(options) {
81
180
  const at = logPosition(room.roomVersion);
82
181
  if (entry === undefined || at === null)
83
182
  return [];
84
- handed.set(room.locator, heldLinks(room));
183
+ handed.set(room.locator, room.roomVersion);
85
184
  notifications.reach(entry.authority, at);
86
185
  return [presented(entry.authority, at)];
87
186
  });
88
187
  notifications.send(kept);
89
188
  }
90
- function adopted(entry, outcome) {
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);
194
+ }
195
+ async function adopted(entry, outcome) {
91
196
  if (outcome.kind !== 'accepted')
92
197
  return outcome;
93
- const { originals, ...accepted } = outcome;
94
- const links = handed.get(entry.locator);
95
- if (links === undefined)
96
- return accepted;
97
- const follows = followsVersion(links.roomVersion, accepted.roomVersion);
98
- const relinked = acceptedLinks(links, accepted, follows ? accepted.roomVersion : links.roomVersion, originals);
99
- handed.set(entry.locator, relinked.links);
100
- const at = logPosition(accepted.roomVersion);
198
+ const held = handed.get(entry.locator);
199
+ if (held === undefined)
200
+ return outcome;
201
+ const follows = followsVersion(held, outcome.roomVersion);
202
+ if (follows)
203
+ handed.set(entry.locator, outcome.roomVersion);
204
+ const at = logPosition(outcome.roomVersion);
101
205
  if (follows && at !== null)
102
206
  notifications.reach(entry.authority, at);
103
- return relinked.attachments === undefined
104
- ? accepted
105
- : { ...accepted, attachments: relinked.attachments };
207
+ return outcome;
106
208
  }
107
209
  function authorityFor(locator) {
108
210
  return tracked.get(locator);
@@ -121,99 +223,106 @@ export function createAgentsHouseAdapter(options) {
121
223
  return result;
122
224
  return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
123
225
  }
124
- function weight(entry) {
125
- return contentPrefixes(entry).length;
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;
126
237
  }
127
- async function bootstrapped(entries, answer) {
128
- let open = entries.map((entry, ask) => ({ entry, ask }));
238
+ async function bootstrapped(asks, answer) {
239
+ let open = asks.map((asked, ask) => ({ entry: asked.entry, asked, ask }));
129
240
  while (open.length > 0) {
130
241
  const unreached = [];
131
- for (const part of selections(open, ({ entry }) => weight(entry), declared().max_prefixes)) {
242
+ for (const part of selections(open, declared().max_prefixes)) {
243
+ const held = await holding(part.map(({ asked }) => asked));
132
244
  const answered = await transport.door(DOOR_BOOTSTRAP, {
133
- prefixes: part.flatMap(({ entry }) => contentPrefixes(entry)),
245
+ prefixes: part.map(({ entry }) => authorityRoot(entry.handle)),
246
+ ...(held.length === 0 ? {} : { held }),
134
247
  });
135
248
  const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
136
- const envelope = classified[0] === null ? parseEnvelope(answered.body) : null;
249
+ const parted = classified[0] === null ? parseParted(answered.body) : null;
137
250
  for (const [index, asked] of part.entries()) {
138
251
  const failure = classified[index];
139
252
  if (failure !== null && failure !== undefined) {
140
253
  answer(asked.ask, failure);
141
254
  continue;
142
255
  }
143
- if (envelope === null) {
256
+ if (parted === null) {
144
257
  answer(asked.ask, UNCERTAIN_READ);
145
258
  continue;
146
259
  }
147
- const scope = envelope.scopes.find((candidate) => matchesScope(candidate, asked.entry));
148
- 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) {
149
262
  unreached.push(asked);
150
263
  continue;
151
264
  }
152
- answer(asked.ask, await bootstrappedRoom(envelope, scope, asked.entry, 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
+ }));
153
276
  }
154
277
  }
155
278
  open = unreached;
156
279
  }
157
280
  }
158
- async function continuedBootstrap(entry, continued) {
159
- const after = { room: entry.authority, path: continued.after };
160
- const answered = await transport.door(DOOR_BOOTSTRAP, {
161
- prefixes: contentPrefixes(entry),
162
- after,
163
- });
164
- const failure = classifyDoor(answered.status, answered.body, entry.locator);
165
- if (failure !== null)
166
- return failure;
167
- const envelope = parseEnvelope(answered.body);
168
- if (envelope === null)
169
- return UNCERTAIN_READ;
170
- const scope = envelope.scopes.find((candidate) => matchesScope(candidate, entry));
171
- return bootstrappedRoom(envelope, scope, entry, continued);
172
- }
173
281
  function layered(asks) {
174
282
  const depths = new Map();
175
283
  const layers = [];
176
284
  for (const ask of asks) {
177
- const depth = depths.get(ask.entry.authority) ?? 0;
178
- depths.set(ask.entry.authority, depth + 1);
285
+ const depth = depths.get(ask.ask.entry.authority) ?? 0;
286
+ depths.set(ask.ask.entry.authority, depth + 1);
179
287
  (layers[depth] ??= []).push(ask);
180
288
  }
181
289
  return layers;
182
290
  }
183
291
  async function enumerated(asks) {
184
- const answers = new Map();
185
- const parts = layered(asks).flatMap((layer) => selections(layer, (ask) => weight(ask.entry), declared().max_prefixes));
292
+ const answers = [];
293
+ const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, declared().max_prefixes));
186
294
  for (const part of parts) {
187
295
  let open = [...part];
188
296
  while (open.length > 0) {
189
297
  const answer = await transport.door(DOOR_ENUMERATE, {
190
- prefixes: open.flatMap((ask) => contentPrefixes(ask.entry)),
191
- positions: open.map((ask) => presented(ask.entry.authority, ask.from)),
298
+ prefixes: open.map(({ ask }) => authorityRoot(ask.entry.handle)),
299
+ positions: open.map(({ ask }) => presented(ask.entry.authority, ask.from)),
192
300
  });
193
- const classified = open.map((ask) => classifyRead(answer.status, answer.body, ask.entry.locator));
301
+ const classified = open.map(({ ask }) => classifyRead(answer.status, answer.body, ask.entry.locator));
194
302
  if (classified[0]?.kind === 'beyond-horizon') {
195
303
  const named = doorRefusal(answer.body)?.fields.authority;
196
- const expired = open.filter((ask) => ask.entry.authority === named);
304
+ const expired = open.filter(({ ask }) => ask.entry.authority === named);
197
305
  const answered = expired.length === 0 ? UNCERTAIN_READ : { kind: 'beyond-horizon' };
198
306
  const gone = expired.length === 0 ? open : expired;
199
307
  for (const ask of gone)
200
- answers.set(ask, answered);
308
+ answers[ask.index] = answered;
201
309
  open = open.filter((ask) => !gone.includes(ask));
202
310
  continue;
203
311
  }
204
312
  const enumeration = classified[0] === null ? parseEnumeration(answer.body, 'credential') : null;
205
- for (const [index, ask] of open.entries()) {
206
- const failure = classified[index];
313
+ for (const [answerIndex, { ask, index }] of open.entries()) {
314
+ const failure = classified[answerIndex];
207
315
  if (failure !== null && failure !== undefined) {
208
- answers.set(ask, failure);
316
+ answers[index] = failure;
209
317
  continue;
210
318
  }
211
- answers.set(ask, enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask));
319
+ answers[index] =
320
+ enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask);
212
321
  }
213
322
  open = [];
214
323
  }
215
324
  }
216
- return asks.map((ask) => answers.get(ask) ?? UNCERTAIN_READ);
325
+ return answers;
217
326
  }
218
327
  const bootstrapTogether = gatheredEach(bootstrapped);
219
328
  const enumerateTogether = gathered(enumerated);
@@ -225,10 +334,12 @@ export function createAgentsHouseAdapter(options) {
225
334
  }
226
335
  const minted = await transport.door(DOOR_BATCH_UPLOAD, { ...head, paths, bytes, sha256 });
227
336
  if (minted.status !== 200) {
228
- 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
+ };
229
340
  }
230
341
  const grant = parseGrant(minted.body);
231
- if (grant === null || grant.method !== 'POST')
342
+ if (grant === null)
232
343
  return { outcome: UNCERTAIN };
233
344
  const delivered = await transport.transfer({
234
345
  act: 'authored_batch_upload',
@@ -238,13 +349,31 @@ export function createAgentsHouseAdapter(options) {
238
349
  if (delivered.status !== 200)
239
350
  return { outcome: UNCERTAIN };
240
351
  const holding = parseGrant(delivered.body);
241
- if (holding === null || holding.method !== 'POST')
352
+ if (holding === null)
242
353
  return { outcome: UNCERTAIN };
243
354
  const upload = { url: holding.url, operation: holding.operation, bytes, sha256 };
244
355
  await keep(upload);
245
356
  return { body: { ...head, paths, upload, runtime: notifications.runtime } };
246
357
  }
247
- async function submit(head, changes, delta, bytes, entry, handed, keep) {
358
+ async function staged(answer, { changeBatch: batch, file }) {
359
+ const refusal = doorRefusal(answer.body);
360
+ if (refusal?.code !== 'files_unstaged')
361
+ return null;
362
+ const uploads = refusal.fields.upload;
363
+ if (!Array.isArray(uploads))
364
+ return false;
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)
369
+ return false;
370
+ const finished = await finishedUpload(transport, upload, await file(change.localId));
371
+ if (finished?.body?.staged !== true)
372
+ return false;
373
+ }
374
+ return true;
375
+ }
376
+ async function submit(request, head, changes, delta, bytes, entry, handed, keep, into) {
248
377
  const inline = { ...head, changes, runtime: notifications.runtime };
249
378
  const prepared = uploadable(inline, bytes, declared())
250
379
  ? await uploaded(head, changes, delta, bytes, entry, handed, keep)
@@ -252,14 +381,81 @@ export function createAgentsHouseAdapter(options) {
252
381
  if ('outcome' in prepared)
253
382
  return prepared.outcome;
254
383
  const answer = await transport.door(DOOR_BATCH, prepared.body);
255
- return mutationOutcome(answer.status, answer.body, entry.locator, entry.handle);
384
+ const stagedFiles = await staged(answer, request);
385
+ if (stagedFiles === false)
386
+ return UNCERTAIN;
387
+ const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
388
+ if (stagedFiles === true && (await staged(settled, request)) !== null)
389
+ return UNCERTAIN;
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
+ }));
396
+ }
397
+ function addressed(answer, locator) {
398
+ const failure = classifyDoor(answer.status, answer.body, locator);
399
+ if (failure !== null) {
400
+ return failure.kind === 'scoped-refusal'
401
+ ? {
402
+ kind: 'rejected',
403
+ code: failure.code,
404
+ fields: failure.fields,
405
+ retryable: failure.retryable,
406
+ }
407
+ : failure;
408
+ }
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)
430
+ return UNCERTAIN_READ;
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
+ });
256
447
  }
257
448
  return {
449
+ roomTypes: HOUSE_ROOM_TYPES,
450
+ nameableRoomTypes: HOUSE_NAMEABLE_ROOM_TYPES,
258
451
  async authorize() {
259
452
  return options.authorize();
260
453
  },
261
- async discover(_authorization) {
262
- 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
+ });
263
459
  const failed = discoveryFailure(answer.status, answer.body);
264
460
  if (failed !== null)
265
461
  return failed;
@@ -268,26 +464,42 @@ export function createAgentsHouseAdapter(options) {
268
464
  return { kind: 'transport-uncertainty' };
269
465
  bounds = document.bounds;
270
466
  const entries = trackedFrom(document);
271
- 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);
272
482
  for (const entry of entries)
273
483
  tracked.set(entry.locator, entry);
274
484
  for (const locator of handed.keys())
275
485
  if (!tracked.has(locator))
276
486
  handed.delete(locator);
277
- notifications.track(entries);
487
+ notifications.track([...tracked.values()]);
278
488
  return {
279
- ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document)),
489
+ ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions, [...tracked.values()])),
280
490
  member: document.member,
491
+ ...(covered === null ? {} : { covered }),
281
492
  };
282
493
  },
283
494
  async bootstrap(_authorization, request) {
284
495
  const entry = authorityFor(request.locator);
285
496
  if (entry === undefined)
286
497
  return { kind: 'room-not-readable', locator: request.locator };
498
+ const asked = { entry, held: request.held };
287
499
  if (request.pageToken !== undefined) {
288
- return continuedBootstrap(entry, JSON.parse(request.pageToken));
500
+ return partPage(asked, JSON.parse(request.pageToken), request.received, request.arriving);
289
501
  }
290
- return bootstrapTogether(entry);
502
+ return bootstrapTogether(asked);
291
503
  },
292
504
  async refresh(_authorization, request) {
293
505
  const entry = authorityFor(request.locator);
@@ -307,17 +519,27 @@ export function createAgentsHouseAdapter(options) {
307
519
  const entry = authorityFor(request.locator);
308
520
  if (entry === undefined)
309
521
  return { kind: 'room-not-readable', locator: request.locator };
310
- 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);
311
531
  if (mapped.kind === 'unmapped')
312
532
  return rejectedMapping(mapped);
313
- const head = operationHead(request.changeBatch);
533
+ const head = operationHead(batch);
314
534
  const delta = deltaBody(mapped.changes);
315
535
  const bytes = deltaBytes(delta);
316
536
  const handed = request.upload;
317
- const outcome = await submit(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);
318
540
  if (handed === undefined || !lapsedGrant(outcome))
319
- return adopted(entry, outcome);
320
- return adopted(entry, await submit(head, mapped.changes, delta, bytes, entry, undefined, keep));
541
+ return settle(outcome);
542
+ return settle(await answer(undefined));
321
543
  },
322
544
  async history(_authorization, request) {
323
545
  const entry = authorityFor(request.locator);
@@ -334,6 +556,18 @@ export function createAgentsHouseAdapter(options) {
334
556
  ...(request.revision === undefined ? {} : { revision: request.revision }),
335
557
  });
336
558
  },
559
+ async binary(_authorization, request) {
560
+ const ask = pathAddress(request);
561
+ if (ask === null)
562
+ return { kind: 'room-not-readable', locator: request.locator };
563
+ return downloaded(ask, request.locator);
564
+ },
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);
570
+ },
337
571
  reached(locator, version, target) {
338
572
  return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
339
573
  },
@@ -343,7 +577,19 @@ export function createAgentsHouseAdapter(options) {
343
577
  async action(_authorization, request) {
344
578
  return withAt(await houseAction(transport, request));
345
579
  },
346
- roomStream: { hold: held, reopen: notifications.reopen, observe: notifications.observe },
580
+ roomStream: {
581
+ hold: held,
582
+ reopen: notifications.reopen,
583
+ observe(notify, receipts, revisions) {
584
+ heldRevisions = revisions;
585
+ return notifications.observe(notify, receipts);
586
+ },
587
+ published(locator, roomVersion) {
588
+ const at = logPosition(roomVersion);
589
+ if (at !== null)
590
+ notifications.published(locator, at);
591
+ },
592
+ },
347
593
  conversations: notifications.conversations,
348
594
  serviceMode: notifications.serviceMode,
349
595
  };
@@ -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"}
@@ -0,0 +1,2 @@
1
+ import { CROCKFORD_CHARACTER } from './crockford.js';
2
+ export const APP_REFERENCE_PATTERN = new RegExp(`^a_(${CROCKFORD_CHARACTER}+)\\.(${CROCKFORD_CHARACTER})$`);