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