@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.
- package/README.md +2 -2
- package/dist/actions.d.ts +4 -69
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +7 -357
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +204 -411
- package/dist/app-reference.d.ts +2 -0
- package/dist/app-reference.d.ts.map +1 -0
- package/dist/app-reference.js +2 -0
- package/dist/crockford.d.ts +4 -0
- package/dist/crockford.d.ts.map +1 -0
- package/dist/crockford.js +3 -0
- package/dist/discovery.d.ts +21 -13
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +73 -53
- package/dist/history.d.ts +12 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +92 -0
- package/dist/house-model.d.ts +719 -0
- package/dist/house-model.d.ts.map +1 -0
- package/dist/house-model.js +393 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/model.d.ts +5 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +10 -29
- package/dist/part.d.ts +14 -0
- package/dist/part.d.ts.map +1 -0
- package/dist/part.js +90 -0
- package/dist/read.d.ts +35 -59
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js +178 -266
- package/dist/refusal.d.ts +0 -3
- package/dist/refusal.d.ts.map +1 -1
- package/dist/refusal.js +0 -5
- package/dist/room-stream.d.ts +19 -0
- package/dist/room-stream.d.ts.map +1 -0
- package/dist/room-stream.js +57 -0
- package/dist/room-types.d.ts +7 -0
- package/dist/room-types.d.ts.map +1 -0
- package/dist/room-types.js +123 -0
- package/dist/search.d.ts +5 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +47 -0
- package/dist/site.d.ts.map +1 -1
- package/dist/site.js +83 -193
- package/dist/stream.d.ts +13 -13
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +166 -411
- package/dist/transport.d.ts +54 -40
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +54 -11
- package/dist/upload.d.ts +12 -0
- package/dist/upload.d.ts.map +1 -0
- package/dist/upload.js +63 -0
- package/dist/write.d.ts +6 -1
- package/dist/write.d.ts.map +1 -1
- package/dist/write.js +28 -39
- package/package.json +8 -4
- package/AGENTS.md +0 -304
- package/dist/gather.d.ts +0 -4
- package/dist/gather.d.ts.map +0 -1
- package/dist/gather.js +0 -35
- package/dist/handed.d.ts +0 -15
- package/dist/handed.d.ts.map +0 -1
- package/dist/handed.js +0 -45
- package/dist/referrer.d.ts +0 -7
- package/dist/referrer.d.ts.map +0 -1
- package/dist/referrer.js +0 -35
package/dist/adapter.js
CHANGED
|
@@ -1,67 +1,49 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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,
|
|
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 {
|
|
7
|
+
import { finishedUpload, houseUpload } from './upload.js';
|
|
9
8
|
import { houseAction } from './actions.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
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 {
|
|
15
|
-
import {
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
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
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
-
return
|
|
99
|
-
const
|
|
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
|
-
|
|
102
|
-
|
|
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
|
-
...(
|
|
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
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
],
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
128
|
+
notifications.send(rooms.flatMap((room) => {
|
|
174
129
|
const entry = tracked.get(room.locator);
|
|
175
130
|
const at = logPosition(room.roomVersion);
|
|
176
|
-
|
|
177
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
370
|
-
if (!Array.isArray(
|
|
189
|
+
const uploads = refusal.fields.upload;
|
|
190
|
+
if (!Array.isArray(uploads))
|
|
371
191
|
return false;
|
|
372
|
-
for (const
|
|
373
|
-
const
|
|
374
|
-
const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === sha256);
|
|
375
|
-
if (
|
|
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
|
|
378
|
-
|
|
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(
|
|
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(
|
|
396
|
-
const stagedFiles = await staged(answer,
|
|
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(
|
|
400
|
-
if (stagedFiles === true && (await staged(settled,
|
|
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
|
|
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
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
return
|
|
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
|
-
|
|
429
|
-
const
|
|
430
|
-
if (
|
|
431
|
-
return
|
|
432
|
-
|
|
433
|
-
|
|
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 {
|
|
271
|
+
return {
|
|
272
|
+
kind: 'binary',
|
|
273
|
+
body: read.body,
|
|
274
|
+
mediaType: address.mediaType,
|
|
275
|
+
sha256: address.sha256,
|
|
276
|
+
};
|
|
436
277
|
}
|
|
437
|
-
function
|
|
278
|
+
function pathAddress(request) {
|
|
438
279
|
const entry = authorityFor(request.locator);
|
|
439
280
|
if (entry === undefined)
|
|
440
281
|
return null;
|
|
441
|
-
return transport.door(
|
|
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
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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(
|
|
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
|
|
539
|
-
return
|
|
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(
|
|
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
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
|
575
|
-
if (
|
|
350
|
+
const ask = pathAddress(request);
|
|
351
|
+
if (ask === null)
|
|
576
352
|
return { kind: 'room-not-readable', locator: request.locator };
|
|
577
|
-
return
|
|
353
|
+
return linked(await ask(), request.locator);
|
|
578
354
|
},
|
|
579
|
-
|
|
580
|
-
const
|
|
581
|
-
if (
|
|
582
|
-
return
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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,
|