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