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

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