@agentshouse/mdruntime-adapter 0.1.0-cf6a7cf003c4b → 0.1.0-cf863e7a50d6e

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 (69) hide show
  1. package/README.md +2 -2
  2. package/dist/actions.d.ts +3 -42
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +3 -294
  5. package/dist/adapter.d.ts +1 -0
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/adapter.js +277 -230
  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 +14 -12
  15. package/dist/discovery.d.ts.map +1 -1
  16. package/dist/discovery.js +65 -50
  17. package/dist/history.d.ts +12 -0
  18. package/dist/history.d.ts.map +1 -0
  19. package/dist/history.js +91 -0
  20. package/dist/house-model.d.ts +955 -0
  21. package/dist/house-model.d.ts.map +1 -0
  22. package/dist/house-model.js +521 -0
  23. package/dist/index.d.ts +10 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +9 -1
  26. package/dist/model.d.ts +5 -3
  27. package/dist/model.d.ts.map +1 -1
  28. package/dist/model.js +10 -32
  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 +50 -44
  33. package/dist/read.d.ts.map +1 -1
  34. package/dist/read.js +245 -214
  35. package/dist/refusal.d.ts +0 -3
  36. package/dist/refusal.d.ts.map +1 -1
  37. package/dist/refusal.js +2 -6
  38. package/dist/room-stream.d.ts +19 -0
  39. package/dist/room-stream.d.ts.map +1 -0
  40. package/dist/room-stream.js +57 -0
  41. package/dist/room-types.d.ts +7 -0
  42. package/dist/room-types.d.ts.map +1 -0
  43. package/dist/room-types.js +125 -0
  44. package/dist/site.d.ts +1 -1
  45. package/dist/site.d.ts.map +1 -1
  46. package/dist/site.js +120 -104
  47. package/dist/stream.d.ts +12 -12
  48. package/dist/stream.d.ts.map +1 -1
  49. package/dist/stream.js +201 -151
  50. package/dist/transport.d.ts +87 -20
  51. package/dist/transport.d.ts.map +1 -1
  52. package/dist/transport.js +54 -9
  53. package/dist/upload.d.ts +12 -0
  54. package/dist/upload.d.ts.map +1 -0
  55. package/dist/upload.js +63 -0
  56. package/dist/version.d.ts +1 -0
  57. package/dist/version.d.ts.map +1 -1
  58. package/dist/version.js +1 -1
  59. package/dist/write.d.ts +19 -10
  60. package/dist/write.d.ts.map +1 -1
  61. package/dist/write.js +141 -44
  62. package/package.json +8 -5
  63. package/AGENTS.md +0 -269
  64. package/dist/gather.d.ts +0 -4
  65. package/dist/gather.d.ts.map +0 -1
  66. package/dist/gather.js +0 -45
  67. package/dist/links.d.ts +0 -20
  68. package/dist/links.d.ts.map +0 -1
  69. package/dist/links.js +0 -104
package/dist/adapter.js CHANGED
@@ -1,108 +1,133 @@
1
- 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';
1
+ import { stepped } from '@agentshouse/mdruntime';
2
+ import { capabilities, discoveredRoom, mapDiscovery, parseAuthorityChange, parseDiscovery, trackedFrom, } from './discovery.js';
4
3
  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';
4
+ import { authorityRoot, bootstrapping, parseFileAddress, parseGrant, } from './read.js';
5
+ import { roomStreams } from './room-stream.js';
6
+ import { sha256Hex } from './digest.js';
7
+ import { finishedUpload, houseUpload } from './upload.js';
7
8
  import { houseAction } from './actions.js';
8
- import { classifyDoor, classifyRead, discoveryFailure, doorRefusal } from './refusal.js';
9
- 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';
12
- import { followsVersion, logPosition, presented, reachedVersion, roomVersion, } from './version.js';
9
+ import { classifyDoor, doorRefusal } from './refusal.js';
10
+ import { houseNotifications, } from './stream.js';
11
+ import { houseDoorRoute, } from './transport.js';
12
+ import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, houseMove, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
13
+ import { HOUSE_NAMEABLE_ROOM_TYPES, HOUSE_ROOM_TYPES } from './room-types.js';
14
+ import { followsVersion, logPosition, presented, reachedVersion, roomVersion } from './version.js';
13
15
  const UNCERTAIN = { kind: 'transport-uncertainty' };
14
16
  const UNCERTAIN_READ = { kind: 'transport-uncertainty' };
17
+ const OCTET_STREAM = 'application/octet-stream';
18
+ const EXPIRED_ADDRESS = 403;
15
19
  function lapsedGrant(outcome) {
16
20
  return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
17
21
  }
22
+ async function heldFiles(authority, held, bound) {
23
+ const listed = await held.files(null, bound + 1);
24
+ const files = listed.slice(0, bound);
25
+ const through = listed.length > bound ? (files.at(-1)?.path ?? null) : null;
26
+ return files.length === 0 ? null : { room: authority, through, files };
27
+ }
28
+ async function reused(scope, held) {
29
+ const sent = new Set(scope.contents.map((entry) => entry.path));
30
+ const contents = [...scope.contents];
31
+ const asked = scope.files.filter((file) => file.binary === undefined && !sent.has(file.path));
32
+ if (asked.length === 0)
33
+ return scope;
34
+ if (held === undefined)
35
+ return null;
36
+ const kept = await held.contents(asked.map(({ path }) => path));
37
+ for (const file of asked) {
38
+ const found = kept.get(file.path);
39
+ if (found === undefined ||
40
+ found.revision !== file.revision ||
41
+ (await sha256Hex(found.content)) !== file.sha256) {
42
+ return null;
43
+ }
44
+ contents.push({ path: file.path, content: found.content });
45
+ }
46
+ return { ...scope, contents };
47
+ }
18
48
  export function createAgentsHouseAdapter(options) {
19
49
  if ('site' in options)
20
- return createSiteAdapter(options.site);
50
+ return createSiteAdapter(options.site, options.snapshot === true);
21
51
  const transport = options.transport;
22
- const notifications = houseNotifications(transport, 'credential');
52
+ const notifications = houseNotifications(transport, 'credential', {
53
+ opened: async (frame) => (frame === null ? null : opened(frame)),
54
+ changed,
55
+ });
23
56
  const tracked = new Map();
24
- const handed = new Map();
57
+ const rooms = roomStreams('credential');
58
+ const unreused = new Set();
25
59
  let bounds;
26
60
  function declared() {
27
61
  if (bounds === undefined)
28
62
  throw new Error('no Discovery declared the bounds');
29
63
  return bounds;
30
64
  }
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));
37
- else
38
- handed.delete(entry.locator);
39
- 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 };
65
+ function opened(frame) {
66
+ const document = parseDiscovery(frame);
67
+ if (document === null)
68
+ return null;
69
+ bounds = document.bounds;
70
+ tracked.clear();
71
+ for (const entry of trackedFrom(document))
72
+ tracked.set(entry.locator, entry);
73
+ notifications.track([...tracked.values()]);
46
74
  return {
47
- ...bootstrapOf(scope),
48
- ...(models === undefined ? {} : { models }),
49
- ...(continuation === null ? {} : { pageToken: JSON.stringify(continuation) }),
75
+ ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions)),
76
+ member: document.member,
50
77
  };
51
78
  }
52
- async function enumeratedRoom(enumeration, ask) {
53
- const view = viewOf(enumeration, ask.entry);
54
- if (view === undefined)
55
- return { kind: 'room-not-readable', locator: ask.entry.locator };
56
- notifications.reach(ask.entry.authority, view);
57
- const answer = refreshFrom(view, ask.fromRoomVersion);
58
- if (answer.kind !== 'net-advancement')
59
- return answer;
60
- handed.set(ask.entry.locator, enumeratedLinks(view, handed.get(ask.entry.locator)));
61
- const models = await viewModels(view, ask.entry);
62
- return models === undefined ? answer : { ...answer, models };
63
- }
64
- async function streamed(entry, request, from) {
65
- const links = handed.get(entry.locator);
66
- if (links?.roomVersion !== request.fromRoomVersion)
67
- return null;
68
- const advanced = notifications.advance(entry, from, (path, revision) => links.revisions.get(path) === revision);
69
- if (advanced === null)
79
+ function changed(frame) {
80
+ const change = parseAuthorityChange(frame);
81
+ if (change === null)
70
82
  return null;
71
- const relinked = streamedLinks(links, advanced);
72
- if (relinked === null)
73
- return null;
74
- handed.set(entry.locator, relinked);
75
- const models = await streamedModels(advanced.schemas, entry, 'credential');
76
- return models === undefined ? advanced.page : { ...advanced.page, models };
83
+ for (const locator of change.left)
84
+ tracked.delete(locator);
85
+ for (const entry of trackedFrom(change))
86
+ tracked.set(entry.locator, entry);
87
+ notifications.track([...tracked.values()]);
88
+ return {
89
+ kind: 'authority-changed',
90
+ rooms: change.authorities.map((authority) => discoveredRoom(authority)),
91
+ left: change.left,
92
+ member: change.member,
93
+ };
94
+ }
95
+ async function streamedPage(entry, token, held) {
96
+ const part = await rooms.part(entry, token);
97
+ if ('kind' in part)
98
+ return part;
99
+ const scope = await reused(part.scope, held);
100
+ if (scope === null) {
101
+ unreused.add(entry.locator);
102
+ return UNCERTAIN_READ;
103
+ }
104
+ return {
105
+ ...(await stepped(bootstrapping(scope))),
106
+ ...(part.models === undefined ? {} : { models: part.models }),
107
+ ...(part.pageToken === undefined ? {} : { pageToken: part.pageToken }),
108
+ bytes: part.bytes,
109
+ };
110
+ }
111
+ async function openedRoom(entry, request) {
112
+ const reusing = unreused.delete(entry.locator) ? undefined : request.held;
113
+ const files = reusing === undefined
114
+ ? null
115
+ : await heldFiles(entry.authority, reusing, declared().max_held_files);
116
+ const answered = await transport.streamed(houseDoorRoute('bootstrap'), {
117
+ room_ref: entry.authority,
118
+ prefixes: [authorityRoot(entry.handle)],
119
+ ...(files === null ? {} : { held: [files] }),
120
+ }, request.signal ?? new AbortController().signal);
121
+ if (answered.room === null)
122
+ return classifyDoor(answered.status, answered.body, entry.locator) ?? UNCERTAIN_READ;
123
+ return rooms.head(entry, answered.room, request.arriving, request.signal);
77
124
  }
78
125
  function held(rooms) {
79
- const kept = rooms.flatMap((room) => {
126
+ notifications.send(rooms.flatMap((room) => {
80
127
  const entry = tracked.get(room.locator);
81
128
  const at = logPosition(room.roomVersion);
82
- if (entry === undefined || at === null)
83
- return [];
84
- handed.set(room.locator, heldLinks(room));
85
- notifications.reach(entry.authority, at);
86
- return [presented(entry.authority, at)];
87
- });
88
- notifications.send(kept);
89
- }
90
- function adopted(entry, outcome) {
91
- if (outcome.kind !== 'accepted')
92
- 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);
101
- if (follows && at !== null)
102
- notifications.reach(entry.authority, at);
103
- return relinked.attachments === undefined
104
- ? accepted
105
- : { ...accepted, attachments: relinked.attachments };
129
+ return entry === undefined || at === null ? [] : [presented(entry.authority, at)];
130
+ }));
106
131
  }
107
132
  function authorityFor(locator) {
108
133
  return tracked.get(locator);
@@ -121,114 +146,25 @@ export function createAgentsHouseAdapter(options) {
121
146
  return result;
122
147
  return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
123
148
  }
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
- function layered(asks) {
174
- const depths = new Map();
175
- const layers = [];
176
- for (const ask of asks) {
177
- const depth = depths.get(ask.entry.authority) ?? 0;
178
- depths.set(ask.entry.authority, depth + 1);
179
- (layers[depth] ??= []).push(ask);
180
- }
181
- return layers;
182
- }
183
- 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));
186
- for (const part of parts) {
187
- let open = [...part];
188
- while (open.length > 0) {
189
- 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)),
192
- });
193
- const classified = open.map((ask) => classifyRead(answer.status, answer.body, ask.entry.locator));
194
- if (classified[0]?.kind === 'beyond-horizon') {
195
- const named = doorRefusal(answer.body)?.fields.authority;
196
- const expired = open.filter((ask) => ask.entry.authority === named);
197
- const answered = expired.length === 0 ? UNCERTAIN_READ : { kind: 'beyond-horizon' };
198
- const gone = expired.length === 0 ? open : expired;
199
- for (const ask of gone)
200
- answers.set(ask, answered);
201
- open = open.filter((ask) => !gone.includes(ask));
202
- continue;
203
- }
204
- const enumeration = classified[0] === null ? parseEnumeration(answer.body, 'credential') : null;
205
- for (const [index, ask] of open.entries()) {
206
- const failure = classified[index];
207
- if (failure !== null && failure !== undefined) {
208
- answers.set(ask, failure);
209
- continue;
210
- }
211
- answers.set(ask, enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask));
212
- }
213
- open = [];
214
- }
215
- }
216
- return asks.map((ask) => answers.get(ask) ?? UNCERTAIN_READ);
217
- }
218
- const bootstrapTogether = gatheredEach(bootstrapped);
219
- const enumerateTogether = gathered(enumerated);
220
149
  async function uploaded(head, changes, delta, bytes, entry, handed, keep) {
221
150
  const paths = batchPaths(changes);
222
151
  const sha256 = await deltaDigest(delta);
223
152
  if (handed !== undefined && handed.sha256 === sha256) {
224
153
  return { body: { ...head, paths, upload: handed, runtime: notifications.runtime } };
225
154
  }
226
- const minted = await transport.door(DOOR_BATCH_UPLOAD, { ...head, paths, bytes, sha256 });
155
+ const minted = await transport.door(houseDoorRoute('batch-upload'), {
156
+ ...head,
157
+ paths,
158
+ bytes,
159
+ sha256,
160
+ });
227
161
  if (minted.status !== 200) {
228
- return { outcome: mutationOutcome(minted.status, minted.body, entry.locator, entry.handle) };
162
+ return {
163
+ outcome: await stepped(mutationOutcome(minted.status, minted.body, entry.locator, entry.handle)),
164
+ };
229
165
  }
230
166
  const grant = parseGrant(minted.body);
231
- if (grant === null || grant.method !== 'POST')
167
+ if (grant === null)
232
168
  return { outcome: UNCERTAIN };
233
169
  const delivered = await transport.transfer({
234
170
  act: 'authored_batch_upload',
@@ -238,92 +174,159 @@ export function createAgentsHouseAdapter(options) {
238
174
  if (delivered.status !== 200)
239
175
  return { outcome: UNCERTAIN };
240
176
  const holding = parseGrant(delivered.body);
241
- if (holding === null || holding.method !== 'POST')
177
+ if (holding === null)
242
178
  return { outcome: UNCERTAIN };
243
179
  const upload = { url: holding.url, operation: holding.operation, bytes, sha256 };
244
180
  await keep(upload);
245
181
  return { body: { ...head, paths, upload, runtime: notifications.runtime } };
246
182
  }
247
- async function submit(head, changes, delta, bytes, entry, handed, keep) {
183
+ async function staged(answer, { changeBatch: batch, file }) {
184
+ const refusal = doorRefusal(answer.body);
185
+ if (refusal?.code !== 'files_unstaged')
186
+ return null;
187
+ const uploads = refusal.fields.upload;
188
+ if (!Array.isArray(uploads))
189
+ return false;
190
+ for (const named of uploads) {
191
+ const upload = houseUpload(named);
192
+ const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === named.sha256);
193
+ if (upload === null || change === undefined)
194
+ return false;
195
+ const finished = await finishedUpload(transport, upload, await file(change.localId));
196
+ if (finished?.body?.staged !== true)
197
+ return false;
198
+ }
199
+ return true;
200
+ }
201
+ async function submit(request, head, changes, delta, bytes, entry, handed, keep, into) {
248
202
  const inline = { ...head, changes, runtime: notifications.runtime };
249
203
  const prepared = uploadable(inline, bytes, declared())
250
204
  ? await uploaded(head, changes, delta, bytes, entry, handed, keep)
251
205
  : { body: inline };
252
206
  if ('outcome' in prepared)
253
207
  return prepared.outcome;
254
- const answer = await transport.door(DOOR_BATCH, prepared.body);
255
- return mutationOutcome(answer.status, answer.body, entry.locator, entry.handle);
208
+ const answer = await transport.door(houseDoorRoute('batch'), prepared.body);
209
+ const stagedFiles = await staged(answer, request);
210
+ if (stagedFiles === false)
211
+ return UNCERTAIN;
212
+ const settled = stagedFiles === null ? answer : await transport.door(houseDoorRoute('batch'), prepared.body);
213
+ if (stagedFiles === true && (await staged(settled, request)) !== null)
214
+ return UNCERTAIN;
215
+ return stepped(mutationOutcome(settled.status, settled.body, entry.locator, entry.handle, into === undefined
216
+ ? undefined
217
+ : {
218
+ source: { authority: entry.authority, handle: entry.handle },
219
+ destination: { authority: into.authority, handle: into.handle },
220
+ }));
221
+ }
222
+ function addressed(answer, locator) {
223
+ const failure = classifyDoor(answer.status, answer.body, locator);
224
+ if (failure !== null) {
225
+ return failure.kind === 'scoped-refusal'
226
+ ? {
227
+ kind: 'rejected',
228
+ code: failure.code,
229
+ fields: failure.fields,
230
+ retryable: failure.retryable,
231
+ }
232
+ : failure;
233
+ }
234
+ const address = parseFileAddress(answer.body);
235
+ return address === null ? UNCERTAIN_READ : { kind: 'address', ...address };
236
+ }
237
+ function linked(answer, locator) {
238
+ const address = addressed(answer, locator);
239
+ return address.kind === 'address'
240
+ ? { kind: 'link', url: address.url, name: address.name, mediaType: address.mediaType }
241
+ : address;
242
+ }
243
+ function refusedAttach(answer, locator) {
244
+ const failure = classifyDoor(answer.status, answer.body, locator);
245
+ if (failure === null)
246
+ return null;
247
+ return failure.kind === 'scoped-refusal'
248
+ ? {
249
+ kind: 'rejected',
250
+ code: failure.code,
251
+ fields: failure.fields,
252
+ retryable: failure.retryable,
253
+ }
254
+ : failure;
255
+ }
256
+ async function downloaded(ask, locator) {
257
+ let address = addressed(await ask(), locator);
258
+ if (address.kind !== 'address')
259
+ return address;
260
+ let read = await transport.download(address.url);
261
+ if (read.status === EXPIRED_ADDRESS) {
262
+ address = addressed(await ask(), locator);
263
+ if (address.kind !== 'address')
264
+ return address;
265
+ read = await transport.download(address.url);
266
+ }
267
+ if (read.status !== 200 || read.body === null)
268
+ return UNCERTAIN_READ;
269
+ return {
270
+ kind: 'binary',
271
+ body: read.body,
272
+ mediaType: address.mediaType,
273
+ sha256: address.sha256,
274
+ };
275
+ }
276
+ function pathAddress(request) {
277
+ const entry = authorityFor(request.locator);
278
+ if (entry === undefined)
279
+ return null;
280
+ return () => transport.door(houseDoorRoute('files/get'), {
281
+ room_ref: entry.authority,
282
+ path: request.path,
283
+ revision: request.revision,
284
+ });
256
285
  }
257
286
  return {
287
+ roomTypes: HOUSE_ROOM_TYPES,
288
+ nameableRoomTypes: HOUSE_NAMEABLE_ROOM_TYPES,
258
289
  async authorize() {
259
290
  return options.authorize();
260
291
  },
261
- async discover(_authorization) {
262
- const answer = await transport.door(DOOR_DISCOVER, { prefixes: [...DISCOVER_PREFIXES] });
263
- const failed = discoveryFailure(answer.status, answer.body);
264
- if (failed !== null)
265
- return failed;
266
- const document = parseDiscovery(answer.body);
267
- if (document === null)
268
- return { kind: 'transport-uncertainty' };
269
- bounds = document.bounds;
270
- const entries = trackedFrom(document);
271
- tracked.clear();
272
- for (const entry of entries)
273
- tracked.set(entry.locator, entry);
274
- for (const locator of handed.keys())
275
- if (!tracked.has(locator))
276
- handed.delete(locator);
277
- notifications.track(entries);
278
- return {
279
- ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document)),
280
- member: document.member,
281
- };
282
- },
283
292
  async bootstrap(_authorization, request) {
284
293
  const entry = authorityFor(request.locator);
285
294
  if (entry === undefined)
286
295
  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);
291
- },
292
- async refresh(_authorization, request) {
293
- const entry = authorityFor(request.locator);
294
- if (entry === undefined)
295
- return { kind: 'room-not-readable', locator: request.locator };
296
- if (request.pageToken !== undefined)
297
- return { kind: 'transport-uncertainty' };
298
- const from = logPosition(request.fromRoomVersion);
299
- if (from === null)
300
- return { kind: 'beyond-horizon' };
301
- const answered = await streamed(entry, request, from);
302
- if (answered !== null)
303
- return answered;
304
- return enumerateTogether({ entry, from, fromRoomVersion: request.fromRoomVersion });
296
+ return request.pageToken === undefined
297
+ ? openedRoom(entry, request)
298
+ : streamedPage(entry, request.pageToken, request.held);
305
299
  },
306
300
  async mutate(_authorization, request, keep) {
307
301
  const entry = authorityFor(request.locator);
308
302
  if (entry === undefined)
309
303
  return { kind: 'room-not-readable', locator: request.locator };
310
- const mapped = houseChanges(request.changeBatch, entry.handle);
304
+ const batch = request.changeBatch;
305
+ const destination = batch.between?.destination.room;
306
+ const into = destination === undefined ? undefined : authorityFor(destination);
307
+ if (destination !== undefined && into === undefined) {
308
+ return { kind: 'room-not-readable', locator: destination };
309
+ }
310
+ const mapped = into === undefined
311
+ ? houseChanges(batch, entry.handle)
312
+ : houseMove(batch, entry.handle, into.handle);
311
313
  if (mapped.kind === 'unmapped')
312
314
  return rejectedMapping(mapped);
313
- const head = operationHead(request.changeBatch);
315
+ const head = operationHead(batch, entry.authority);
314
316
  const delta = deltaBody(mapped.changes);
315
317
  const bytes = deltaBytes(delta);
316
318
  const handed = request.upload;
317
- const outcome = await submit(head, mapped.changes, delta, bytes, entry, handed, keep);
319
+ const answer = (upload) => submit(request, head, mapped.changes, delta, bytes, entry, upload, keep, into);
320
+ const outcome = await answer(handed);
318
321
  if (handed === undefined || !lapsedGrant(outcome))
319
- return adopted(entry, outcome);
320
- return adopted(entry, await submit(head, mapped.changes, delta, bytes, entry, undefined, keep));
322
+ return outcome;
323
+ return answer(undefined);
321
324
  },
322
325
  async history(_authorization, request) {
323
326
  const entry = authorityFor(request.locator);
324
327
  if (entry === undefined)
325
328
  return { kind: 'room-not-readable', locator: request.locator };
326
- const answer = await transport.door(DOOR_REVISIONS, revisionBody({
329
+ const answer = await transport.door(houseDoorRoute('revisions'), revisionBody({
327
330
  path: request.path,
328
331
  handle: entry.handle,
329
332
  ...(request.revision === undefined ? {} : { revision: request.revision }),
@@ -334,6 +337,47 @@ export function createAgentsHouseAdapter(options) {
334
337
  ...(request.revision === undefined ? {} : { revision: request.revision }),
335
338
  });
336
339
  },
340
+ async binary(_authorization, request) {
341
+ const ask = pathAddress(request);
342
+ if (ask === null)
343
+ return { kind: 'room-not-readable', locator: request.locator };
344
+ return downloaded(ask, request.locator);
345
+ },
346
+ async link(_authorization, request) {
347
+ const ask = pathAddress(request);
348
+ if (ask === null)
349
+ return { kind: 'room-not-readable', locator: request.locator };
350
+ return linked(await ask(), request.locator);
351
+ },
352
+ async attach(_authorization, request) {
353
+ const entry = authorityFor(request.locator);
354
+ if (entry === undefined)
355
+ return { kind: 'room-not-readable', locator: request.locator };
356
+ const declared = await transport.door(houseDoorRoute('files/attach'), {
357
+ room_ref: entry.authority,
358
+ name: request.name,
359
+ bytes: request.byteLength,
360
+ sha256: request.sha256,
361
+ media_type: request.mediaType.length > 0 ? request.mediaType : OCTET_STREAM,
362
+ });
363
+ const refused = refusedAttach(declared, request.locator);
364
+ if (refused !== null)
365
+ return refused;
366
+ const upload = houseUpload(declared.body?.upload);
367
+ if (upload === null)
368
+ return UNCERTAIN_READ;
369
+ const finished = await finishedUpload(transport, upload, request.blob);
370
+ if (finished === null)
371
+ return UNCERTAIN_READ;
372
+ const unfinished = refusedAttach(finished, request.locator);
373
+ if (unfinished !== null)
374
+ return unfinished;
375
+ const { path, revision } = (finished.body ?? {});
376
+ const root = `/rooms/${entry.handle}/`;
377
+ return typeof path === 'string' && path.startsWith(root) && typeof revision === 'string'
378
+ ? { kind: 'attached', path: path.slice(root.length), revision }
379
+ : UNCERTAIN_READ;
380
+ },
337
381
  reached(locator, version, target) {
338
382
  return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
339
383
  },
@@ -343,7 +387,10 @@ export function createAgentsHouseAdapter(options) {
343
387
  async action(_authorization, request) {
344
388
  return withAt(await houseAction(transport, request));
345
389
  },
346
- roomStream: { hold: held, reopen: notifications.reopen, observe: notifications.observe },
390
+ roomStream: {
391
+ hold: held,
392
+ observe: notifications.observe,
393
+ },
347
394
  conversations: notifications.conversations,
348
395
  serviceMode: notifications.serviceMode,
349
396
  };
@@ -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)}]`;