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