@agentshouse/mdruntime-adapter 0.1.0-cb89e0425af50 → 0.1.0-cb8fc491b1a92

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