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