@agentshouse/mdruntime-adapter 0.1.0-cafd7caa7ae65 → 0.1.0-cb0bbc4ccacd0

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