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