@agentshouse/mdruntime-adapter 0.1.0-cd59fd8945dcb → 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 (65) hide show
  1. package/README.md +2 -2
  2. package/dist/actions.d.ts +13 -7
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +37 -114
  5. package/dist/adapter.d.ts +1 -0
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/adapter.js +330 -130
  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 +44 -36
  17. package/dist/gather.d.ts +1 -2
  18. package/dist/gather.d.ts.map +1 -1
  19. package/dist/gather.js +5 -15
  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 +486 -0
  23. package/dist/index.d.ts +8 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +7 -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 +14 -0
  30. package/dist/part.d.ts.map +1 -0
  31. package/dist/part.js +90 -0
  32. package/dist/read.d.ts +72 -23
  33. package/dist/read.d.ts.map +1 -1
  34. package/dist/read.js +352 -150
  35. package/dist/refusal.d.ts.map +1 -1
  36. package/dist/refusal.js +2 -1
  37. package/dist/room-stream.d.ts +19 -0
  38. package/dist/room-stream.d.ts.map +1 -0
  39. package/dist/room-stream.js +57 -0
  40. package/dist/room-types.d.ts +7 -0
  41. package/dist/room-types.d.ts.map +1 -0
  42. package/dist/room-types.js +125 -0
  43. package/dist/site.d.ts +1 -1
  44. package/dist/site.d.ts.map +1 -1
  45. package/dist/site.js +123 -61
  46. package/dist/stream.d.ts +5 -4
  47. package/dist/stream.d.ts.map +1 -1
  48. package/dist/stream.js +356 -43
  49. package/dist/transport.d.ts +71 -8
  50. package/dist/transport.d.ts.map +1 -1
  51. package/dist/transport.js +4 -0
  52. package/dist/upload.d.ts +12 -0
  53. package/dist/upload.d.ts.map +1 -0
  54. package/dist/upload.js +63 -0
  55. package/dist/version.d.ts +1 -0
  56. package/dist/version.d.ts.map +1 -1
  57. package/dist/version.js +1 -1
  58. package/dist/write.d.ts +17 -9
  59. package/dist/write.d.ts.map +1 -1
  60. package/dist/write.js +138 -40
  61. package/package.json +8 -5
  62. package/AGENTS.md +0 -290
  63. package/dist/links.d.ts +0 -20
  64. package/dist/links.d.ts.map +0 -1
  65. package/dist/links.js +0 -104
package/dist/adapter.js CHANGED
@@ -1,77 +1,146 @@
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, } from './read.js';
6
- import { acceptedLinks, enumeratedLinks, heldLinks, scopeLinks, streamedLinks, } from './links.js';
6
+ import { authorityRoot, bootstrapping, parseEnumeration, parseFileAddress, parseGrant, refreshFrom, viewOf, wholeView, } from './read.js';
7
+ import { roomStreams } from './room-stream.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_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';
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 OCTET_STREAM = 'application/octet-stream';
20
+ const EXPIRED_ADDRESS = 403;
15
21
  function lapsedGrant(outcome) {
16
22
  return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
17
23
  }
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 };
29
+ }
30
+ async function reused(scope, held) {
31
+ const sent = new Set(scope.contents.map((entry) => entry.path));
32
+ const contents = [...scope.contents];
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) {
44
+ return null;
45
+ }
46
+ contents.push({ path: file.path, content: found.content });
47
+ }
48
+ return { ...scope, contents };
49
+ }
18
50
  export function createAgentsHouseAdapter(options) {
19
51
  if ('site' in options)
20
- return createSiteAdapter(options.site);
52
+ return createSiteAdapter(options.site, options.snapshot === true);
21
53
  const transport = options.transport;
22
54
  const notifications = houseNotifications(transport, 'credential');
23
55
  const tracked = new Map();
24
56
  const handed = new Map();
57
+ const rooms = roomStreams('credential');
58
+ const unreused = new Set();
59
+ let heldRevisions;
25
60
  let bounds;
26
61
  function declared() {
27
62
  if (bounds === undefined)
28
63
  throw new Error('no Discovery declared the bounds');
29
64
  return bounds;
30
65
  }
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));
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);
71
+ if (scope === null) {
72
+ unreused.add(entry.locator);
73
+ return UNCERTAIN_READ;
74
+ }
75
+ if (part.first && part.ended)
76
+ handed.set(entry.locator, roomVersion(scope));
37
77
  else
38
78
  handed.delete(entry.locator);
39
79
  notifications.reach(entry.authority, scope);
40
- const seen = [
41
- ...(continued?.seen ?? []),
42
- ...entry.schemas.filter((schema) => scope.files.some((file) => file.path === schema)),
43
- ];
44
- const models = await partModels(scope, entry, new Set(seen), next === null);
45
- const continuation = next === null ? null : { after: next, seen };
46
80
  return {
47
- ...bootstrapOf(scope),
48
- ...(models === undefined ? {} : { models }),
49
- ...(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,
50
85
  };
51
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
+ }
100
+ async function enumeratedPage(ask, page) {
101
+ const answer = await transport.door(DOOR_ENUMERATE, {
102
+ prefixes: [authorityRoot(ask.entry.handle)],
103
+ positions: [
104
+ {
105
+ ...presented(ask.entry.authority, { position: page.after, logEpoch: ask.from.logEpoch }),
106
+ page: { through: page.through, read: page.read },
107
+ },
108
+ ],
109
+ });
110
+ const failure = classifyRead(answer.status, answer.body, ask.entry.locator);
111
+ if (failure !== null)
112
+ return failure;
113
+ const enumeration = parseEnumeration(answer.body, 'credential');
114
+ if (enumeration === null)
115
+ return UNCERTAIN_READ;
116
+ return (viewOf(enumeration, ask.entry) ?? { kind: 'room-not-readable', locator: ask.entry.locator });
117
+ }
52
118
  async function enumeratedRoom(enumeration, ask) {
53
- const view = viewOf(enumeration, ask.entry);
54
- if (view === undefined)
119
+ const first = viewOf(enumeration, ask.entry);
120
+ if (first === undefined)
55
121
  return { kind: 'room-not-readable', locator: ask.entry.locator };
122
+ const view = await wholeView(first, (page) => enumeratedPage(ask, page));
123
+ if ('kind' in view)
124
+ return view;
56
125
  notifications.reach(ask.entry.authority, view);
57
- const answer = refreshFrom(view, ask.fromRoomVersion);
126
+ const answer = await refreshFrom(view, ask.fromRoomVersion);
58
127
  if (answer.kind !== 'net-advancement')
59
128
  return answer;
60
- handed.set(ask.entry.locator, enumeratedLinks(view, handed.get(ask.entry.locator)));
129
+ handed.set(ask.entry.locator, roomVersion(view));
61
130
  const models = await viewModels(view, ask.entry);
62
131
  return models === undefined ? answer : { ...answer, models };
63
132
  }
64
133
  async function streamed(entry, request, from) {
65
- const links = handed.get(entry.locator);
66
- if (links?.roomVersion !== request.fromRoomVersion)
134
+ if (handed.get(entry.locator) !== request.fromRoomVersion)
67
135
  return null;
68
- const advanced = notifications.advance(entry, from, (path, revision) => links.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));
69
141
  if (advanced === null)
70
142
  return null;
71
- const relinked = streamedLinks(links, advanced);
72
- if (relinked === null)
73
- return null;
74
- handed.set(entry.locator, relinked);
143
+ handed.set(entry.locator, advanced.page.roomVersion);
75
144
  const models = await streamedModels(advanced.schemas, entry, 'credential');
76
145
  return models === undefined ? advanced.page : { ...advanced.page, models };
77
146
  }
@@ -81,28 +150,31 @@ export function createAgentsHouseAdapter(options) {
81
150
  const at = logPosition(room.roomVersion);
82
151
  if (entry === undefined || at === null)
83
152
  return [];
84
- handed.set(room.locator, heldLinks(room));
153
+ handed.set(room.locator, room.roomVersion);
85
154
  notifications.reach(entry.authority, at);
86
155
  return [presented(entry.authority, at)];
87
156
  });
88
157
  notifications.send(kept);
89
158
  }
90
- 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) {
91
166
  if (outcome.kind !== 'accepted')
92
167
  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);
168
+ const held = handed.get(entry.locator);
169
+ if (held === undefined)
170
+ return outcome;
171
+ const follows = followsVersion(held, outcome.roomVersion);
172
+ if (follows)
173
+ handed.set(entry.locator, outcome.roomVersion);
174
+ const at = logPosition(outcome.roomVersion);
101
175
  if (follows && at !== null)
102
176
  notifications.reach(entry.authority, at);
103
- return relinked.attachments === undefined
104
- ? accepted
105
- : { ...accepted, attachments: relinked.attachments };
177
+ return outcome;
106
178
  }
107
179
  function authorityFor(locator) {
108
180
  return tracked.get(locator);
@@ -121,101 +193,52 @@ export function createAgentsHouseAdapter(options) {
121
193
  return result;
122
194
  return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
123
195
  }
124
- function weight(entry) {
125
- return contentPrefixes(entry).length;
126
- }
127
- async function bootstrapped(entries, answer) {
128
- let open = entries.map((entry, ask) => ({ entry, ask }));
129
- while (open.length > 0) {
130
- const unreached = [];
131
- for (const part of selections(open, ({ entry }) => weight(entry), declared().max_prefixes)) {
132
- const answered = await transport.door(DOOR_BOOTSTRAP, {
133
- prefixes: part.flatMap(({ entry }) => contentPrefixes(entry)),
134
- });
135
- const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
136
- const envelope = classified[0] === null ? parseEnvelope(answered.body) : null;
137
- for (const [index, asked] of part.entries()) {
138
- const failure = classified[index];
139
- if (failure !== null && failure !== undefined) {
140
- answer(asked.ask, failure);
141
- continue;
142
- }
143
- if (envelope === null) {
144
- answer(asked.ask, UNCERTAIN_READ);
145
- continue;
146
- }
147
- const scope = envelope.scopes.find((candidate) => matchesScope(candidate, asked.entry));
148
- if (scope === undefined && envelope.next !== undefined) {
149
- unreached.push(asked);
150
- continue;
151
- }
152
- answer(asked.ask, await bootstrappedRoom(envelope, scope, asked.entry, null));
153
- }
154
- }
155
- open = unreached;
156
- }
157
- }
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
196
  function layered(asks) {
174
197
  const depths = new Map();
175
198
  const layers = [];
176
199
  for (const ask of asks) {
177
- const depth = depths.get(ask.entry.authority) ?? 0;
178
- depths.set(ask.entry.authority, depth + 1);
200
+ const depth = depths.get(ask.ask.entry.authority) ?? 0;
201
+ depths.set(ask.ask.entry.authority, depth + 1);
179
202
  (layers[depth] ??= []).push(ask);
180
203
  }
181
204
  return layers;
182
205
  }
183
206
  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));
207
+ const answers = [];
208
+ const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, declared().max_prefixes));
186
209
  for (const part of parts) {
187
210
  let open = [...part];
188
211
  while (open.length > 0) {
189
212
  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)),
213
+ prefixes: open.map(({ ask }) => authorityRoot(ask.entry.handle)),
214
+ positions: open.map(({ ask }) => presented(ask.entry.authority, ask.from)),
192
215
  });
193
- const classified = open.map((ask) => classifyRead(answer.status, answer.body, ask.entry.locator));
216
+ const classified = open.map(({ ask }) => classifyRead(answer.status, answer.body, ask.entry.locator));
194
217
  if (classified[0]?.kind === 'beyond-horizon') {
195
218
  const named = doorRefusal(answer.body)?.fields.authority;
196
- const expired = open.filter((ask) => ask.entry.authority === named);
219
+ const expired = open.filter(({ ask }) => ask.entry.authority === named);
197
220
  const answered = expired.length === 0 ? UNCERTAIN_READ : { kind: 'beyond-horizon' };
198
221
  const gone = expired.length === 0 ? open : expired;
199
222
  for (const ask of gone)
200
- answers.set(ask, answered);
223
+ answers[ask.index] = answered;
201
224
  open = open.filter((ask) => !gone.includes(ask));
202
225
  continue;
203
226
  }
204
227
  const enumeration = classified[0] === null ? parseEnumeration(answer.body, 'credential') : null;
205
- for (const [index, ask] of open.entries()) {
206
- const failure = classified[index];
228
+ for (const [answerIndex, { ask, index }] of open.entries()) {
229
+ const failure = classified[answerIndex];
207
230
  if (failure !== null && failure !== undefined) {
208
- answers.set(ask, failure);
231
+ answers[index] = failure;
209
232
  continue;
210
233
  }
211
- answers.set(ask, enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask));
234
+ answers[index] =
235
+ enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask);
212
236
  }
213
237
  open = [];
214
238
  }
215
239
  }
216
- return asks.map((ask) => answers.get(ask) ?? UNCERTAIN_READ);
240
+ return answers;
217
241
  }
218
- const bootstrapTogether = gatheredEach(bootstrapped);
219
242
  const enumerateTogether = gathered(enumerated);
220
243
  async function uploaded(head, changes, delta, bytes, entry, handed, keep) {
221
244
  const paths = batchPaths(changes);
@@ -225,10 +248,12 @@ export function createAgentsHouseAdapter(options) {
225
248
  }
226
249
  const minted = await transport.door(DOOR_BATCH_UPLOAD, { ...head, paths, bytes, sha256 });
227
250
  if (minted.status !== 200) {
228
- 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
+ };
229
254
  }
230
255
  const grant = parseGrant(minted.body);
231
- if (grant === null || grant.method !== 'POST')
256
+ if (grant === null)
232
257
  return { outcome: UNCERTAIN };
233
258
  const delivered = await transport.transfer({
234
259
  act: 'authored_batch_upload',
@@ -238,13 +263,31 @@ export function createAgentsHouseAdapter(options) {
238
263
  if (delivered.status !== 200)
239
264
  return { outcome: UNCERTAIN };
240
265
  const holding = parseGrant(delivered.body);
241
- if (holding === null || holding.method !== 'POST')
266
+ if (holding === null)
242
267
  return { outcome: UNCERTAIN };
243
268
  const upload = { url: holding.url, operation: holding.operation, bytes, sha256 };
244
269
  await keep(upload);
245
270
  return { body: { ...head, paths, upload, runtime: notifications.runtime } };
246
271
  }
247
- async function submit(head, changes, delta, bytes, entry, handed, keep) {
272
+ async function staged(answer, { changeBatch: batch, file }) {
273
+ const refusal = doorRefusal(answer.body);
274
+ if (refusal?.code !== 'files_unstaged')
275
+ return null;
276
+ const uploads = refusal.fields.upload;
277
+ if (!Array.isArray(uploads))
278
+ return false;
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)
283
+ return false;
284
+ const finished = await finishedUpload(transport, upload, await file(change.localId));
285
+ if (finished?.body?.staged !== true)
286
+ return false;
287
+ }
288
+ return true;
289
+ }
290
+ async function submit(request, head, changes, delta, bytes, entry, handed, keep, into) {
248
291
  const inline = { ...head, changes, runtime: notifications.runtime };
249
292
  const prepared = uploadable(inline, bytes, declared())
250
293
  ? await uploaded(head, changes, delta, bytes, entry, handed, keep)
@@ -252,14 +295,94 @@ export function createAgentsHouseAdapter(options) {
252
295
  if ('outcome' in prepared)
253
296
  return prepared.outcome;
254
297
  const answer = await transport.door(DOOR_BATCH, prepared.body);
255
- return mutationOutcome(answer.status, answer.body, entry.locator, entry.handle);
298
+ const stagedFiles = await staged(answer, request);
299
+ if (stagedFiles === false)
300
+ return UNCERTAIN;
301
+ const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
302
+ if (stagedFiles === true && (await staged(settled, request)) !== null)
303
+ return UNCERTAIN;
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
+ }));
310
+ }
311
+ function addressed(answer, locator) {
312
+ const failure = classifyDoor(answer.status, answer.body, locator);
313
+ if (failure !== null) {
314
+ return failure.kind === 'scoped-refusal'
315
+ ? {
316
+ kind: 'rejected',
317
+ code: failure.code,
318
+ fields: failure.fields,
319
+ retryable: failure.retryable,
320
+ }
321
+ : failure;
322
+ }
323
+ const address = parseFileAddress(answer.body);
324
+ return address === null ? UNCERTAIN_READ : { kind: 'address', ...address };
325
+ }
326
+ function linked(answer, locator) {
327
+ const address = addressed(answer, locator);
328
+ return address.kind === 'address'
329
+ ? { kind: 'link', url: address.url, name: address.name, mediaType: address.mediaType }
330
+ : address;
331
+ }
332
+ function refusedAttach(answer, locator) {
333
+ const failure = classifyDoor(answer.status, answer.body, locator);
334
+ if (failure === null)
335
+ return null;
336
+ return failure.kind === 'scoped-refusal'
337
+ ? {
338
+ kind: 'rejected',
339
+ code: failure.code,
340
+ fields: failure.fields,
341
+ retryable: failure.retryable,
342
+ }
343
+ : failure;
344
+ }
345
+ async function downloaded(ask, locator) {
346
+ let address = addressed(await ask(), locator);
347
+ if (address.kind !== 'address')
348
+ return address;
349
+ let read = await transport.download(address.url);
350
+ if (read.status === EXPIRED_ADDRESS) {
351
+ address = addressed(await ask(), locator);
352
+ if (address.kind !== 'address')
353
+ return address;
354
+ read = await transport.download(address.url);
355
+ }
356
+ if (read.status !== 200 || read.body === null)
357
+ return UNCERTAIN_READ;
358
+ return {
359
+ kind: 'binary',
360
+ body: read.body,
361
+ mediaType: address.mediaType,
362
+ sha256: address.sha256,
363
+ };
364
+ }
365
+ function pathAddress(request) {
366
+ const entry = authorityFor(request.locator);
367
+ if (entry === undefined)
368
+ return null;
369
+ return () => transport.door(DOOR_FILES_GET, {
370
+ room_ref: entry.authority,
371
+ path: request.path,
372
+ revision: request.revision,
373
+ });
256
374
  }
257
375
  return {
376
+ roomTypes: HOUSE_ROOM_TYPES,
377
+ nameableRoomTypes: HOUSE_NAMEABLE_ROOM_TYPES,
258
378
  async authorize() {
259
379
  return options.authorize();
260
380
  },
261
- async discover(_authorization) {
262
- const answer = await transport.door(DOOR_DISCOVER, { prefixes: [...DISCOVER_PREFIXES] });
381
+ async discover(_authorization, scope) {
382
+ const answer = await transport.door(DOOR_DISCOVER, {
383
+ prefixes: [...DISCOVER_PREFIXES],
384
+ ...(scope === undefined ? {} : { rooms: scope.authorities }),
385
+ });
263
386
  const failed = discoveryFailure(answer.status, answer.body);
264
387
  if (failed !== null)
265
388
  return failed;
@@ -268,26 +391,40 @@ export function createAgentsHouseAdapter(options) {
268
391
  return { kind: 'transport-uncertainty' };
269
392
  bounds = document.bounds;
270
393
  const entries = trackedFrom(document);
271
- tracked.clear();
394
+ const covered = scope === undefined
395
+ ? null
396
+ : [
397
+ ...new Set([
398
+ ...[...tracked.values()]
399
+ .filter((entry) => scope.authorities.includes(entry.authority))
400
+ .map((entry) => entry.locator),
401
+ ...entries.map((entry) => entry.locator),
402
+ ]),
403
+ ];
404
+ if (covered === null)
405
+ tracked.clear();
406
+ else
407
+ for (const locator of covered)
408
+ tracked.delete(locator);
272
409
  for (const entry of entries)
273
410
  tracked.set(entry.locator, entry);
274
411
  for (const locator of handed.keys())
275
412
  if (!tracked.has(locator))
276
413
  handed.delete(locator);
277
- notifications.track(entries);
414
+ notifications.track([...tracked.values()]);
278
415
  return {
279
- ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document)),
416
+ ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions, [...tracked.values()])),
280
417
  member: document.member,
418
+ ...(covered === null ? {} : { covered }),
281
419
  };
282
420
  },
283
421
  async bootstrap(_authorization, request) {
284
422
  const entry = authorityFor(request.locator);
285
423
  if (entry === undefined)
286
424
  return { kind: 'room-not-readable', locator: request.locator };
287
- if (request.pageToken !== undefined) {
288
- return continuedBootstrap(entry, JSON.parse(request.pageToken));
289
- }
290
- return bootstrapTogether(entry);
425
+ return request.pageToken === undefined
426
+ ? openedRoom(entry, request)
427
+ : streamedPage(entry, request.pageToken, request.held);
291
428
  },
292
429
  async refresh(_authorization, request) {
293
430
  const entry = authorityFor(request.locator);
@@ -307,17 +444,27 @@ export function createAgentsHouseAdapter(options) {
307
444
  const entry = authorityFor(request.locator);
308
445
  if (entry === undefined)
309
446
  return { kind: 'room-not-readable', locator: request.locator };
310
- 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);
311
456
  if (mapped.kind === 'unmapped')
312
457
  return rejectedMapping(mapped);
313
- const head = operationHead(request.changeBatch);
458
+ const head = operationHead(batch);
314
459
  const delta = deltaBody(mapped.changes);
315
460
  const bytes = deltaBytes(delta);
316
461
  const handed = request.upload;
317
- const outcome = await submit(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);
318
465
  if (handed === undefined || !lapsedGrant(outcome))
319
- return adopted(entry, outcome);
320
- return adopted(entry, await submit(head, mapped.changes, delta, bytes, entry, undefined, keep));
466
+ return settle(outcome);
467
+ return settle(await answer(undefined));
321
468
  },
322
469
  async history(_authorization, request) {
323
470
  const entry = authorityFor(request.locator);
@@ -334,6 +481,47 @@ export function createAgentsHouseAdapter(options) {
334
481
  ...(request.revision === undefined ? {} : { revision: request.revision }),
335
482
  });
336
483
  },
484
+ async binary(_authorization, request) {
485
+ const ask = pathAddress(request);
486
+ if (ask === null)
487
+ return { kind: 'room-not-readable', locator: request.locator };
488
+ return downloaded(ask, request.locator);
489
+ },
490
+ async link(_authorization, request) {
491
+ const ask = pathAddress(request);
492
+ if (ask === null)
493
+ return { kind: 'room-not-readable', locator: request.locator };
494
+ return linked(await ask(), request.locator);
495
+ },
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
+ },
337
525
  reached(locator, version, target) {
338
526
  return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
339
527
  },
@@ -343,7 +531,19 @@ export function createAgentsHouseAdapter(options) {
343
531
  async action(_authorization, request) {
344
532
  return withAt(await houseAction(transport, request));
345
533
  },
346
- roomStream: { hold: held, reopen: notifications.reopen, observe: notifications.observe },
534
+ roomStream: {
535
+ hold: held,
536
+ reopen: notifications.reopen,
537
+ observe(notify, receipts, revisions) {
538
+ heldRevisions = revisions;
539
+ return notifications.observe(notify, receipts);
540
+ },
541
+ published(locator, roomVersion) {
542
+ const at = logPosition(roomVersion);
543
+ if (at !== null)
544
+ notifications.published(locator, at);
545
+ },
546
+ },
347
547
  conversations: notifications.conversations,
348
548
  serviceMode: notifications.serviceMode,
349
549
  };
@@ -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)}]`;