@agentshouse/mdruntime-adapter 0.1.0-cedb8e57837a7 → 0.1.0-cf1ebfb67feeb

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 (70) hide show
  1. package/README.md +2 -2
  2. package/dist/actions.d.ts +4 -69
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +7 -357
  5. package/dist/adapter.d.ts.map +1 -1
  6. package/dist/adapter.js +204 -411
  7. package/dist/app-reference.d.ts +2 -0
  8. package/dist/app-reference.d.ts.map +1 -0
  9. package/dist/app-reference.js +2 -0
  10. package/dist/crockford.d.ts +4 -0
  11. package/dist/crockford.d.ts.map +1 -0
  12. package/dist/crockford.js +3 -0
  13. package/dist/discovery.d.ts +21 -13
  14. package/dist/discovery.d.ts.map +1 -1
  15. package/dist/discovery.js +73 -53
  16. package/dist/history.d.ts +12 -0
  17. package/dist/history.d.ts.map +1 -0
  18. package/dist/history.js +92 -0
  19. package/dist/house-model.d.ts +719 -0
  20. package/dist/house-model.d.ts.map +1 -0
  21. package/dist/house-model.js +393 -0
  22. package/dist/index.d.ts +11 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +10 -1
  25. package/dist/model.d.ts +5 -2
  26. package/dist/model.d.ts.map +1 -1
  27. package/dist/model.js +10 -29
  28. package/dist/part.d.ts +14 -0
  29. package/dist/part.d.ts.map +1 -0
  30. package/dist/part.js +90 -0
  31. package/dist/read.d.ts +35 -59
  32. package/dist/read.d.ts.map +1 -1
  33. package/dist/read.js +178 -266
  34. package/dist/refusal.d.ts +0 -3
  35. package/dist/refusal.d.ts.map +1 -1
  36. package/dist/refusal.js +0 -5
  37. package/dist/room-stream.d.ts +19 -0
  38. package/dist/room-stream.d.ts.map +1 -0
  39. package/dist/room-stream.js +57 -0
  40. package/dist/room-types.d.ts +7 -0
  41. package/dist/room-types.d.ts.map +1 -0
  42. package/dist/room-types.js +123 -0
  43. package/dist/search.d.ts +5 -0
  44. package/dist/search.d.ts.map +1 -0
  45. package/dist/search.js +47 -0
  46. package/dist/site.d.ts.map +1 -1
  47. package/dist/site.js +83 -193
  48. package/dist/stream.d.ts +13 -13
  49. package/dist/stream.d.ts.map +1 -1
  50. package/dist/stream.js +166 -411
  51. package/dist/transport.d.ts +54 -40
  52. package/dist/transport.d.ts.map +1 -1
  53. package/dist/transport.js +54 -11
  54. package/dist/upload.d.ts +12 -0
  55. package/dist/upload.d.ts.map +1 -0
  56. package/dist/upload.js +63 -0
  57. package/dist/write.d.ts +6 -1
  58. package/dist/write.d.ts.map +1 -1
  59. package/dist/write.js +28 -39
  60. package/package.json +8 -4
  61. package/AGENTS.md +0 -304
  62. package/dist/gather.d.ts +0 -4
  63. package/dist/gather.d.ts.map +0 -1
  64. package/dist/gather.js +0 -35
  65. package/dist/handed.d.ts +0 -15
  66. package/dist/handed.d.ts.map +0 -1
  67. package/dist/handed.js +0 -45
  68. package/dist/referrer.d.ts +0 -7
  69. package/dist/referrer.d.ts.map +0 -1
  70. package/dist/referrer.js +0 -35
package/dist/adapter.js CHANGED
@@ -1,67 +1,49 @@
1
- import { sortedKeys, stepped } from '@agentshouse/mdruntime';
2
- import { DISCOVER_PREFIXES, capabilities, discoveredRoom, mapDiscovery, parseDiscovery, trackedFrom, } from './discovery.js';
3
- import { partModels, streamedModels, viewModels } from './model.js';
4
- import { gathered, gatheredEach, selections } from './gather.js';
1
+ import { stepped } from '@agentshouse/mdruntime';
2
+ import { capabilities, discoveredRoom, mapDiscovery, parseAuthorityChange, parseDiscovery, trackedFrom, } from './discovery.js';
5
3
  import { createSiteAdapter } from './site.js';
6
- import { authorityRoot, bootstrapping, matchesScope, parseEnumeration, parseEnvelope, parseGrant, refreshFrom, viewOf, wholeView, } from './read.js';
4
+ import { authorityRoot, bootstrapping, parseFileAddress, parseGrant, } from './read.js';
5
+ import { roomStreams } from './room-stream.js';
7
6
  import { sha256Hex } from './digest.js';
8
- import { acceptedHanded, enumeratedHanded, heldHanded, scopeHanded, streamedHanded, } from './handed.js';
7
+ import { finishedUpload, houseUpload } from './upload.js';
9
8
  import { houseAction } from './actions.js';
10
- import { classifyDoor, classifyRead, discoveryFailure, doorRefusal } from './refusal.js';
11
- import { houseNotifications } from './stream.js';
12
- import { DOOR_BATCH, DOOR_BATCH_UPLOAD, DOOR_BOOTSTRAP, DOOR_DISCOVER, DOOR_ENUMERATE, DOOR_FILES_GET, DOOR_REVISIONS, } from './transport.js';
9
+ import { houseSearch } from './search.js';
10
+ import { classifyDoor, doorRefusal } from './refusal.js';
11
+ import { houseNotifications, } from './stream.js';
12
+ import { houseDoorRoute, } from './transport.js';
13
13
  import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, houseMove, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
14
- import { houseReferrer } from './referrer.js';
15
- import { followsVersion, logPosition, presented, reachedVersion, roomVersion, } from './version.js';
14
+ import { HOUSE_TYPED_PATHS } from './house-model.js';
15
+ import { HOUSE_NAMEABLE_ROOM_TYPES, HOUSE_ROOM_TYPES } from './room-types.js';
16
+ import { followsVersion, logPosition, presented, reachedVersion, roomVersion } from './version.js';
16
17
  const UNCERTAIN = { kind: 'transport-uncertainty' };
17
18
  const UNCERTAIN_READ = { kind: 'transport-uncertainty' };
19
+ const OCTET_STREAM = 'application/octet-stream';
20
+ const EXPIRED_ADDRESS = 403;
18
21
  function lapsedGrant(outcome) {
19
22
  return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
20
23
  }
21
- const heldPaths = new WeakMap();
22
- async function sortedHeld(held) {
23
- const known = heldPaths.get(held.documents);
24
- if (known !== undefined)
25
- return known;
26
- const sorted = await stepped(sortedKeys(held.documents));
27
- heldPaths.set(held.documents, sorted);
28
- return sorted;
29
- }
30
- function heldFiles(authority, held, paths, after, bound) {
31
- let low = 0;
32
- for (let high = paths.length; low < high;) {
33
- const middle = (low + high) >>> 1;
34
- if (after !== null && paths[middle] <= after)
35
- low = middle + 1;
36
- else
37
- high = middle;
38
- }
39
- const files = [];
40
- let through = null;
41
- for (let at = low; at < paths.length && through === null; at += 1) {
42
- const revision = held.revisions[paths[at]];
43
- if (revision === undefined)
44
- continue;
45
- if (files.length === bound)
46
- through = files.at(-1).path;
47
- else
48
- files.push({ path: paths[at], revision });
49
- }
24
+ async function heldFiles(authority, held, bound) {
25
+ const listed = await held.files(null, bound + 1);
26
+ const files = listed.slice(0, bound);
27
+ const through = listed.length > bound ? (files.at(-1)?.path ?? null) : null;
50
28
  return files.length === 0 ? null : { room: authority, through, files };
51
29
  }
52
30
  async function reused(scope, held) {
53
31
  const sent = new Set(scope.contents.map((entry) => entry.path));
54
32
  const contents = [...scope.contents];
55
- for (const file of scope.files) {
56
- if (file.binary !== undefined || sent.has(file.path))
57
- continue;
58
- const content = held?.documents[file.path];
59
- if (content === undefined ||
60
- held?.revisions[file.path] !== file.revision ||
61
- (await sha256Hex(content)) !== file.sha256) {
33
+ const asked = scope.files.filter((file) => file.binary === undefined && !sent.has(file.path));
34
+ if (asked.length === 0)
35
+ return scope;
36
+ if (held === undefined)
37
+ return null;
38
+ const kept = await held.contents(asked.map(({ path }) => path));
39
+ for (const file of asked) {
40
+ const found = kept.get(file.path);
41
+ if (found === undefined ||
42
+ found.revision !== file.revision ||
43
+ (await sha256Hex(found.content)) !== file.sha256) {
62
44
  return null;
63
45
  }
64
- contents.push({ path: file.path, content });
46
+ contents.push({ path: file.path, content: found.content });
65
47
  }
66
48
  return { ...scope, contents };
67
49
  }
@@ -69,136 +51,85 @@ export function createAgentsHouseAdapter(options) {
69
51
  if ('site' in options)
70
52
  return createSiteAdapter(options.site, options.snapshot === true);
71
53
  const transport = options.transport;
72
- const notifications = houseNotifications(transport, 'credential');
54
+ const notifications = houseNotifications(transport, 'credential', {
55
+ opened: async (frame) => (frame === null ? null : opened(frame)),
56
+ changed,
57
+ });
73
58
  const tracked = new Map();
74
- const handed = new Map();
59
+ const rooms = roomStreams('credential');
60
+ const unreused = new Set();
75
61
  let bounds;
76
62
  function declared() {
77
63
  if (bounds === undefined)
78
64
  throw new Error('no Discovery declared the bounds');
79
65
  return bounds;
80
66
  }
81
- function hand(locator, steps) {
82
- const held = handed.get(locator);
83
- const next = stepped(steps);
84
- if (!(next instanceof Promise)) {
85
- handed.set(locator, next);
86
- return;
87
- }
88
- return next.then((room) => {
89
- if (handed.get(locator) === held)
90
- handed.set(locator, room);
91
- else
92
- handed.delete(locator);
93
- });
67
+ function opened(frame) {
68
+ const document = parseDiscovery(frame);
69
+ if (document === null)
70
+ return null;
71
+ bounds = document.bounds;
72
+ tracked.clear();
73
+ for (const entry of trackedFrom(document))
74
+ tracked.set(entry.locator, entry);
75
+ notifications.track([...tracked.values()]);
76
+ return {
77
+ ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions)),
78
+ member: document.member,
79
+ };
94
80
  }
95
- async function bootstrappedRoom(envelope, sent, asked, continued) {
96
- const { entry } = asked;
97
- if (sent === undefined)
98
- return { kind: 'room-not-readable', locator: entry.locator };
99
- const scope = await reused(sent, asked.held);
81
+ function changed(frame) {
82
+ const change = parseAuthorityChange(frame);
83
+ if (change === null)
84
+ return null;
85
+ for (const locator of change.left)
86
+ tracked.delete(locator);
87
+ for (const entry of trackedFrom(change))
88
+ tracked.set(entry.locator, entry);
89
+ notifications.track([...tracked.values()]);
90
+ return {
91
+ kind: 'authority-changed',
92
+ rooms: change.authorities.map((authority) => discoveredRoom(authority)),
93
+ left: change.left,
94
+ member: change.member,
95
+ };
96
+ }
97
+ async function streamedPage(entry, token, held) {
98
+ const part = await rooms.part(entry, token);
99
+ if ('kind' in part)
100
+ return part;
101
+ const scope = await reused(part.scope, held);
100
102
  if (scope === null) {
101
- return asked.held === undefined
102
- ? UNCERTAIN_READ
103
- : continued === null
104
- ? bootstrapTogether({ entry, held: undefined })
105
- : continuedBootstrap({ entry, held: undefined }, continued);
103
+ unreused.add(entry.locator);
104
+ return UNCERTAIN_READ;
106
105
  }
107
- const cursor = envelope.next?.room === entry.authority ? envelope.next : undefined;
108
- const next = cursor?.path ?? null;
109
- if (continued === null && next === null)
110
- await hand(entry.locator, scopeHanded(scope));
111
- else
112
- handed.delete(entry.locator);
113
- notifications.reach(entry.authority, scope);
114
- const seen = [
115
- ...(continued?.seen ?? []),
116
- ...entry.schemas.filter((schema) => scope.files.some((file) => file.path === schema)),
117
- ];
118
- const models = await partModels(scope, entry, new Set(seen), next === null);
119
- const continuation = next === null
120
- ? null
121
- : { after: next, seen, ...(cursor?.cut === undefined ? {} : { cut: cursor.cut }) };
122
106
  return {
123
107
  ...(await stepped(bootstrapping(scope))),
124
- ...(models === undefined ? {} : { models }),
125
- ...(continuation === null ? {} : { pageToken: JSON.stringify(continuation) }),
108
+ ...(part.models === undefined ? {} : { models: part.models }),
109
+ ...(part.pageToken === undefined ? {} : { pageToken: part.pageToken }),
110
+ bytes: part.bytes,
126
111
  };
127
112
  }
128
- async function enumeratedPage(ask, page) {
129
- const answer = await transport.door(DOOR_ENUMERATE, {
130
- prefixes: [authorityRoot(ask.entry.handle)],
131
- positions: [
132
- {
133
- ...presented(ask.entry.authority, { position: page.after, logEpoch: ask.from.logEpoch }),
134
- page: { through: page.through, read: page.read },
135
- },
136
- ],
137
- });
138
- const failure = classifyRead(answer.status, answer.body, ask.entry.locator);
139
- if (failure !== null)
140
- return failure;
141
- const enumeration = parseEnumeration(answer.body, 'credential');
142
- if (enumeration === null)
143
- return UNCERTAIN_READ;
144
- return (viewOf(enumeration, ask.entry) ?? { kind: 'room-not-readable', locator: ask.entry.locator });
145
- }
146
- async function enumeratedRoom(enumeration, ask) {
147
- const first = viewOf(enumeration, ask.entry);
148
- if (first === undefined)
149
- return { kind: 'room-not-readable', locator: ask.entry.locator };
150
- const view = await wholeView(first, (page) => enumeratedPage(ask, page));
151
- if ('kind' in view)
152
- return view;
153
- notifications.reach(ask.entry.authority, view);
154
- const answer = await refreshFrom(view, ask.fromRoomVersion);
155
- if (answer.kind !== 'net-advancement')
156
- return answer;
157
- await hand(ask.entry.locator, enumeratedHanded(view, handed.get(ask.entry.locator)));
158
- const models = await viewModels(view, ask.entry);
159
- return models === undefined ? answer : { ...answer, models };
160
- }
161
- async function streamed(entry, request, from) {
162
- const held = handed.get(entry.locator);
163
- if (held?.roomVersion !== request.fromRoomVersion)
164
- return null;
165
- const advanced = await stepped(notifications.advance(entry, from, (path, revision) => held.revisions[path] === revision));
166
- if (advanced === null)
167
- return null;
168
- await hand(entry.locator, streamedHanded(held, advanced));
169
- const models = await streamedModels(advanced.schemas, entry, 'credential');
170
- return models === undefined ? advanced.page : { ...advanced.page, models };
113
+ async function openedRoom(entry, request) {
114
+ const reusing = unreused.delete(entry.locator) ? undefined : request.held;
115
+ const files = reusing === undefined
116
+ ? null
117
+ : await heldFiles(entry.authority, reusing, declared().max_held_files);
118
+ const answered = await transport.streamed(houseDoorRoute('bootstrap'), {
119
+ room_ref: entry.authority,
120
+ prefixes: [authorityRoot(entry.handle)],
121
+ ...(files === null ? {} : { held: [files] }),
122
+ }, request.signal ?? new AbortController().signal);
123
+ if (answered.room === null)
124
+ return classifyDoor(answered.status, answered.body, entry.locator) ?? UNCERTAIN_READ;
125
+ return rooms.head(entry, answered.room, request.arriving, request.signal);
171
126
  }
172
127
  function held(rooms) {
173
- const kept = rooms.flatMap((room) => {
128
+ notifications.send(rooms.flatMap((room) => {
174
129
  const entry = tracked.get(room.locator);
175
130
  const at = logPosition(room.roomVersion);
176
- if (entry === undefined || at === null)
177
- return [];
178
- handed.set(room.locator, heldHanded(room));
179
- notifications.reach(entry.authority, at);
180
- return [presented(entry.authority, at)];
181
- });
182
- notifications.send(kept);
183
- }
184
- async function adoptedMove(entry, into, outcome) {
185
- if (outcome.kind === 'accepted' && outcome.destination !== undefined) {
186
- await adopted(into, { kind: 'accepted', ...outcome.destination });
187
- }
188
- return adopted(entry, outcome);
189
- }
190
- async function adopted(entry, outcome) {
191
- if (outcome.kind !== 'accepted')
192
- return outcome;
193
- const held = handed.get(entry.locator);
194
- if (held === undefined)
195
- return outcome;
196
- const follows = followsVersion(held.roomVersion, outcome.roomVersion);
197
- await hand(entry.locator, acceptedHanded(held, outcome, follows ? outcome.roomVersion : held.roomVersion));
198
- const at = logPosition(outcome.roomVersion);
199
- if (follows && at !== null)
200
- notifications.reach(entry.authority, at);
201
- return outcome;
131
+ return entry === undefined || at === null ? [] : [presented(entry.authority, at)];
132
+ }));
202
133
  }
203
134
  function authorityFor(locator) {
204
135
  return tracked.get(locator);
@@ -217,136 +148,25 @@ export function createAgentsHouseAdapter(options) {
217
148
  return result;
218
149
  return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
219
150
  }
220
- async function holding(asks, after) {
221
- const bound = Math.floor(declared().max_held_files / Math.max(1, asks.filter((ask) => ask.held !== undefined).length));
222
- const held = [];
223
- for (const ask of asks) {
224
- if (ask.held === undefined || bound === 0)
225
- continue;
226
- const paths = await sortedHeld(ask.held);
227
- const files = heldFiles(ask.entry.authority, ask.held, paths, after, bound);
228
- if (files !== null)
229
- held.push(files);
230
- }
231
- return held;
232
- }
233
- async function bootstrapped(asks, answer) {
234
- let open = asks.map((asked, ask) => ({ entry: asked.entry, asked, ask }));
235
- while (open.length > 0) {
236
- const unreached = [];
237
- for (const part of selections(open, declared().max_prefixes)) {
238
- const held = await holding(part.map(({ asked }) => asked), null);
239
- const answered = await transport.door(DOOR_BOOTSTRAP, {
240
- prefixes: part.map(({ entry }) => authorityRoot(entry.handle)),
241
- ...(held.length === 0 ? {} : { held }),
242
- });
243
- const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
244
- const envelope = classified[0] === null ? await stepped(parseEnvelope(answered.body)) : null;
245
- for (const [index, asked] of part.entries()) {
246
- const failure = classified[index];
247
- if (failure !== null && failure !== undefined) {
248
- answer(asked.ask, failure);
249
- continue;
250
- }
251
- if (envelope === null) {
252
- answer(asked.ask, UNCERTAIN_READ);
253
- continue;
254
- }
255
- const scope = envelope.scopes.find((candidate) => matchesScope(candidate, asked.entry));
256
- if (scope === undefined && envelope.next !== undefined) {
257
- unreached.push(asked);
258
- continue;
259
- }
260
- answer(asked.ask, await bootstrappedRoom(envelope, scope, asked.asked, null));
261
- }
262
- }
263
- open = unreached;
264
- }
265
- }
266
- async function continuedBootstrap(asked, continued) {
267
- const { entry } = asked;
268
- const after = {
269
- room: entry.authority,
270
- path: continued.after,
271
- ...(continued.cut === undefined ? {} : { cut: continued.cut }),
272
- };
273
- const held = await holding([asked], continued.after);
274
- const answered = await transport.door(DOOR_BOOTSTRAP, {
275
- prefixes: [authorityRoot(entry.handle)],
276
- after,
277
- ...(held.length === 0 ? {} : { held }),
278
- });
279
- const failure = classifyRead(answered.status, answered.body, entry.locator);
280
- if (failure !== null)
281
- return failure;
282
- const envelope = await stepped(parseEnvelope(answered.body));
283
- if (envelope === null)
284
- return UNCERTAIN_READ;
285
- const scope = envelope.scopes.find((candidate) => matchesScope(candidate, entry));
286
- return bootstrappedRoom(envelope, scope, asked, continued);
287
- }
288
- function layered(asks) {
289
- const depths = new Map();
290
- const layers = [];
291
- for (const ask of asks) {
292
- const depth = depths.get(ask.ask.entry.authority) ?? 0;
293
- depths.set(ask.ask.entry.authority, depth + 1);
294
- (layers[depth] ??= []).push(ask);
295
- }
296
- return layers;
297
- }
298
- async function enumerated(asks) {
299
- const answers = [];
300
- const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, declared().max_prefixes));
301
- for (const part of parts) {
302
- let open = [...part];
303
- while (open.length > 0) {
304
- const answer = await transport.door(DOOR_ENUMERATE, {
305
- prefixes: open.map(({ ask }) => authorityRoot(ask.entry.handle)),
306
- positions: open.map(({ ask }) => presented(ask.entry.authority, ask.from)),
307
- });
308
- const classified = open.map(({ ask }) => classifyRead(answer.status, answer.body, ask.entry.locator));
309
- if (classified[0]?.kind === 'beyond-horizon') {
310
- const named = doorRefusal(answer.body)?.fields.authority;
311
- const expired = open.filter(({ ask }) => ask.entry.authority === named);
312
- const answered = expired.length === 0 ? UNCERTAIN_READ : { kind: 'beyond-horizon' };
313
- const gone = expired.length === 0 ? open : expired;
314
- for (const ask of gone)
315
- answers[ask.index] = answered;
316
- open = open.filter((ask) => !gone.includes(ask));
317
- continue;
318
- }
319
- const enumeration = classified[0] === null ? parseEnumeration(answer.body, 'credential') : null;
320
- for (const [answerIndex, { ask, index }] of open.entries()) {
321
- const failure = classified[answerIndex];
322
- if (failure !== null && failure !== undefined) {
323
- answers[index] = failure;
324
- continue;
325
- }
326
- answers[index] =
327
- enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask);
328
- }
329
- open = [];
330
- }
331
- }
332
- return answers;
333
- }
334
- const bootstrapTogether = gatheredEach(bootstrapped);
335
- const enumerateTogether = gathered(enumerated);
336
151
  async function uploaded(head, changes, delta, bytes, entry, handed, keep) {
337
152
  const paths = batchPaths(changes);
338
153
  const sha256 = await deltaDigest(delta);
339
154
  if (handed !== undefined && handed.sha256 === sha256) {
340
155
  return { body: { ...head, paths, upload: handed, runtime: notifications.runtime } };
341
156
  }
342
- const minted = await transport.door(DOOR_BATCH_UPLOAD, { ...head, paths, bytes, sha256 });
157
+ const minted = await transport.door(houseDoorRoute('batch-upload'), {
158
+ ...head,
159
+ paths,
160
+ bytes,
161
+ sha256,
162
+ });
343
163
  if (minted.status !== 200) {
344
164
  return {
345
165
  outcome: await stepped(mutationOutcome(minted.status, minted.body, entry.locator, entry.handle)),
346
166
  };
347
167
  }
348
168
  const grant = parseGrant(minted.body);
349
- if (grant === null || grant.method !== 'POST')
169
+ if (grant === null)
350
170
  return { outcome: UNCERTAIN };
351
171
  const delivered = await transport.transfer({
352
172
  act: 'authored_batch_upload',
@@ -356,48 +176,43 @@ export function createAgentsHouseAdapter(options) {
356
176
  if (delivered.status !== 200)
357
177
  return { outcome: UNCERTAIN };
358
178
  const holding = parseGrant(delivered.body);
359
- if (holding === null || holding.method !== 'POST')
179
+ if (holding === null)
360
180
  return { outcome: UNCERTAIN };
361
181
  const upload = { url: holding.url, operation: holding.operation, bytes, sha256 };
362
182
  await keep(upload);
363
183
  return { body: { ...head, paths, upload, runtime: notifications.runtime } };
364
184
  }
365
- async function staged(answer, batch) {
185
+ async function staged(answer, { changeBatch: batch, file }) {
366
186
  const refusal = doorRefusal(answer.body);
367
187
  if (refusal?.code !== 'files_unstaged')
368
188
  return null;
369
- const grants = refusal.fields.upload;
370
- if (!Array.isArray(grants))
189
+ const uploads = refusal.fields.upload;
190
+ if (!Array.isArray(uploads))
371
191
  return false;
372
- for (const grant of grants) {
373
- const { url, operation, sha256 } = grant;
374
- const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === sha256);
375
- if (typeof url !== 'string' || typeof operation !== 'string' || change === undefined)
192
+ for (const named of uploads) {
193
+ const upload = houseUpload(named);
194
+ const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === named.sha256);
195
+ if (upload === null || change === undefined)
376
196
  return false;
377
- const delivered = await transport.transfer({
378
- act: 'binary_stage',
379
- url,
380
- body: change.blob,
381
- operation,
382
- });
383
- if (delivered.status !== 200)
197
+ const finished = await finishedUpload(transport, upload, await file(change.localId));
198
+ if (finished?.body?.staged !== true)
384
199
  return false;
385
200
  }
386
201
  return true;
387
202
  }
388
- async function submit(batch, head, changes, delta, bytes, entry, handed, keep, into) {
203
+ async function submit(request, head, changes, delta, bytes, entry, handed, keep, into) {
389
204
  const inline = { ...head, changes, runtime: notifications.runtime };
390
205
  const prepared = uploadable(inline, bytes, declared())
391
206
  ? await uploaded(head, changes, delta, bytes, entry, handed, keep)
392
207
  : { body: inline };
393
208
  if ('outcome' in prepared)
394
209
  return prepared.outcome;
395
- const answer = await transport.door(DOOR_BATCH, prepared.body);
396
- const stagedFiles = await staged(answer, batch);
210
+ const answer = await transport.door(houseDoorRoute('batch'), prepared.body);
211
+ const stagedFiles = await staged(answer, request);
397
212
  if (stagedFiles === false)
398
213
  return UNCERTAIN;
399
- const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
400
- if (stagedFiles === true && (await staged(settled, batch)) !== null)
214
+ const settled = stagedFiles === null ? answer : await transport.door(houseDoorRoute('batch'), prepared.body);
215
+ if (stagedFiles === true && (await staged(settled, request)) !== null)
401
216
  return UNCERTAIN;
402
217
  return stepped(mutationOutcome(settled.status, settled.body, entry.locator, entry.handle, into === undefined
403
218
  ? undefined
@@ -406,7 +221,7 @@ export function createAgentsHouseAdapter(options) {
406
221
  destination: { authority: into.authority, handle: into.handle },
407
222
  }));
408
223
  }
409
- function granted(answer, locator) {
224
+ function addressed(answer, locator) {
410
225
  const failure = classifyDoor(answer.status, answer.body, locator);
411
226
  if (failure !== null) {
412
227
  return failure.kind === 'scoped-refusal'
@@ -418,99 +233,72 @@ export function createAgentsHouseAdapter(options) {
418
233
  }
419
234
  : failure;
420
235
  }
421
- const grant = parseGrant(answer.body);
422
- const name = answer.body.name;
423
- if (grant === null || grant.method !== 'GET' || typeof name !== 'string') {
424
- return UNCERTAIN_READ;
425
- }
426
- return { kind: 'link', url: grant.url, name };
236
+ const address = parseFileAddress(answer.body);
237
+ return address === null ? UNCERTAIN_READ : { kind: 'address', ...address };
238
+ }
239
+ function linked(answer, locator) {
240
+ const address = addressed(answer, locator);
241
+ return address.kind === 'address'
242
+ ? { kind: 'link', url: address.url, name: address.name, mediaType: address.mediaType }
243
+ : address;
427
244
  }
428
- async function downloaded(answer, locator) {
429
- const link = granted(answer, locator);
430
- if (link.kind !== 'link')
431
- return link;
432
- const read = await transport.download(link.url);
433
- if (read.status !== 200 || read.blob === null)
245
+ function refusedAttach(answer, locator) {
246
+ const failure = classifyDoor(answer.status, answer.body, locator);
247
+ if (failure === null)
248
+ return null;
249
+ return failure.kind === 'scoped-refusal'
250
+ ? {
251
+ kind: 'rejected',
252
+ code: failure.code,
253
+ fields: failure.fields,
254
+ retryable: failure.retryable,
255
+ }
256
+ : failure;
257
+ }
258
+ async function downloaded(ask, locator) {
259
+ let address = addressed(await ask(), locator);
260
+ if (address.kind !== 'address')
261
+ return address;
262
+ let read = await transport.download(address.url);
263
+ if (read.status === EXPIRED_ADDRESS) {
264
+ address = addressed(await ask(), locator);
265
+ if (address.kind !== 'address')
266
+ return address;
267
+ read = await transport.download(address.url);
268
+ }
269
+ if (read.status !== 200 || read.body === null)
434
270
  return UNCERTAIN_READ;
435
- return { kind: 'binary', blob: read.blob };
271
+ return {
272
+ kind: 'binary',
273
+ body: read.body,
274
+ mediaType: address.mediaType,
275
+ sha256: address.sha256,
276
+ };
436
277
  }
437
- function pathGrant(request) {
278
+ function pathAddress(request) {
438
279
  const entry = authorityFor(request.locator);
439
280
  if (entry === undefined)
440
281
  return null;
441
- return transport.door(DOOR_FILES_GET, {
282
+ return () => transport.door(houseDoorRoute('files/get'), {
442
283
  room_ref: entry.authority,
443
284
  path: request.path,
444
285
  revision: request.revision,
445
286
  });
446
287
  }
447
288
  return {
289
+ roomTypes: HOUSE_ROOM_TYPES,
290
+ nameableRoomTypes: HOUSE_NAMEABLE_ROOM_TYPES,
291
+ typedPaths: HOUSE_TYPED_PATHS,
448
292
  async authorize() {
449
293
  return options.authorize();
450
294
  },
451
- async discover(_authorization, scope) {
452
- const answer = await transport.door(DOOR_DISCOVER, {
453
- prefixes: [...DISCOVER_PREFIXES],
454
- ...(scope === undefined ? {} : { rooms: scope.authorities }),
455
- });
456
- const failed = discoveryFailure(answer.status, answer.body);
457
- if (failed !== null)
458
- return failed;
459
- const document = parseDiscovery(answer.body);
460
- if (document === null)
461
- return { kind: 'transport-uncertainty' };
462
- bounds = document.bounds;
463
- const entries = trackedFrom(document);
464
- const covered = scope === undefined
465
- ? null
466
- : [
467
- ...new Set([
468
- ...[...tracked.values()]
469
- .filter((entry) => scope.authorities.includes(entry.authority))
470
- .map((entry) => entry.locator),
471
- ...entries.map((entry) => entry.locator),
472
- ]),
473
- ];
474
- if (covered === null)
475
- tracked.clear();
476
- else
477
- for (const locator of covered)
478
- tracked.delete(locator);
479
- for (const entry of entries)
480
- tracked.set(entry.locator, entry);
481
- for (const locator of handed.keys())
482
- if (!tracked.has(locator))
483
- handed.delete(locator);
484
- notifications.track([...tracked.values()]);
485
- return {
486
- ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions, [...tracked.values()])),
487
- member: document.member,
488
- ...(covered === null ? {} : { covered }),
489
- };
490
- },
491
295
  async bootstrap(_authorization, request) {
492
296
  const entry = authorityFor(request.locator);
493
297
  if (entry === undefined)
494
298
  return { kind: 'room-not-readable', locator: request.locator };
495
- const asked = { entry, held: request.held };
496
- if (request.pageToken !== undefined) {
497
- return continuedBootstrap(asked, JSON.parse(request.pageToken));
498
- }
499
- return bootstrapTogether(asked);
500
- },
501
- async refresh(_authorization, request) {
502
- const entry = authorityFor(request.locator);
503
- if (entry === undefined)
504
- return { kind: 'room-not-readable', locator: request.locator };
505
- if (request.pageToken !== undefined)
506
- return { kind: 'transport-uncertainty' };
507
- const from = logPosition(request.fromRoomVersion);
508
- if (from === null)
509
- return { kind: 'beyond-horizon' };
510
- const answered = await streamed(entry, request, from);
511
- if (answered !== null)
512
- return answered;
513
- return enumerateTogether({ entry, from, fromRoomVersion: request.fromRoomVersion });
299
+ return request.pageToken === undefined
300
+ ? openedRoom(entry, request)
301
+ : streamedPage(entry, request.pageToken, request.held);
514
302
  },
515
303
  async mutate(_authorization, request, keep) {
516
304
  const entry = authorityFor(request.locator);
@@ -527,22 +315,21 @@ export function createAgentsHouseAdapter(options) {
527
315
  : houseMove(batch, entry.handle, into.handle);
528
316
  if (mapped.kind === 'unmapped')
529
317
  return rejectedMapping(mapped);
530
- const head = operationHead(batch);
318
+ const head = operationHead(batch, entry.authority);
531
319
  const delta = deltaBody(mapped.changes);
532
320
  const bytes = deltaBytes(delta);
533
321
  const handed = request.upload;
534
- const answer = (upload) => submit(batch, head, mapped.changes, delta, bytes, entry, upload, keep, into);
535
- const settle = (outcome) => into === undefined ? adopted(entry, outcome) : adoptedMove(entry, into, outcome);
322
+ const answer = (upload) => submit(request, head, mapped.changes, delta, bytes, entry, upload, keep, into);
536
323
  const outcome = await answer(handed);
537
324
  if (handed === undefined || !lapsedGrant(outcome))
538
- return settle(outcome);
539
- return settle(await answer(undefined));
325
+ return outcome;
326
+ return answer(undefined);
540
327
  },
541
328
  async history(_authorization, request) {
542
329
  const entry = authorityFor(request.locator);
543
330
  if (entry === undefined)
544
331
  return { kind: 'room-not-readable', locator: request.locator };
545
- const answer = await transport.door(DOOR_REVISIONS, revisionBody({
332
+ const answer = await transport.door(houseDoorRoute('revisions'), revisionBody({
546
333
  path: request.path,
547
334
  handle: entry.handle,
548
335
  ...(request.revision === undefined ? {} : { revision: request.revision }),
@@ -554,36 +341,45 @@ export function createAgentsHouseAdapter(options) {
554
341
  });
555
342
  },
556
343
  async binary(_authorization, request) {
557
- if (request.kind === 'path') {
558
- const asked = pathGrant(request);
559
- if (asked === null)
560
- return { kind: 'room-not-readable', locator: request.locator };
561
- return downloaded(await asked, request.locator);
562
- }
563
- const referrer = houseReferrer(request.referrer, tracked);
564
- if (referrer === null) {
565
- return { kind: 'rejected', code: 'referrer_unknown', fields: {}, retryable: false };
566
- }
567
- const answer = await transport.door(DOOR_FILES_GET, {
568
- file: request.fileRef,
569
- referrer: referrer.referrer,
570
- });
571
- return downloaded(answer, referrer.locator);
344
+ const ask = pathAddress(request);
345
+ if (ask === null)
346
+ return { kind: 'room-not-readable', locator: request.locator };
347
+ return downloaded(ask, request.locator);
572
348
  },
573
349
  async link(_authorization, request) {
574
- const asked = pathGrant(request);
575
- if (asked === null)
350
+ const ask = pathAddress(request);
351
+ if (ask === null)
576
352
  return { kind: 'room-not-readable', locator: request.locator };
577
- return granted(await asked, request.locator);
353
+ return linked(await ask(), request.locator);
578
354
  },
579
- referrerScope(referrer) {
580
- const held = houseReferrer(referrer, tracked);
581
- if (held === null)
582
- return null;
583
- if (held.locator === '')
584
- return { kind: 'conversations' };
585
- const { path } = held.referrer;
586
- return { kind: 'room', locator: held.locator, ...(path === undefined ? {} : { path }) };
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;
587
383
  },
588
384
  reached(locator, version, target) {
589
385
  return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
@@ -594,15 +390,12 @@ export function createAgentsHouseAdapter(options) {
594
390
  async action(_authorization, request) {
595
391
  return withAt(await houseAction(transport, request));
596
392
  },
393
+ search(_authorization, request) {
394
+ return houseSearch(transport, [...tracked.values()], request);
395
+ },
597
396
  roomStream: {
598
397
  hold: held,
599
- reopen: notifications.reopen,
600
398
  observe: notifications.observe,
601
- published(locator, roomVersion) {
602
- const at = logPosition(roomVersion);
603
- if (at !== null)
604
- notifications.published(locator, at);
605
- },
606
399
  },
607
400
  conversations: notifications.conversations,
608
401
  serviceMode: notifications.serviceMode,