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