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