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