@agentshouse/mdruntime-adapter 0.1.0-cc63dd043d0e2 → 0.1.0-cc7da2d4669a3

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