@agentshouse/mdruntime-adapter 0.1.0-ca0d16f904a93 → 0.1.0-ca231c2cbedb4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/actions.d.ts +4 -22
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +23 -134
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +292 -171
- 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 +101 -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 +33 -16
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +2 -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 -300
- 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,49 @@
|
|
|
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
|
-
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 {
|
|
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';
|
|
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 OCTET_STREAM = 'application/octet-stream';
|
|
20
|
+
const EXPIRED_ADDRESS = 403;
|
|
17
21
|
function lapsedGrant(outcome) {
|
|
18
22
|
return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
|
|
19
23
|
}
|
|
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 };
|
|
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 };
|
|
30
29
|
}
|
|
31
30
|
async function reused(scope, held) {
|
|
32
31
|
const sent = new Set(scope.contents.map((entry) => entry.path));
|
|
33
32
|
const contents = [...scope.contents];
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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) {
|
|
41
44
|
return null;
|
|
42
45
|
}
|
|
43
|
-
contents.push({ path: file.path, content });
|
|
46
|
+
contents.push({ path: file.path, content: found.content });
|
|
44
47
|
}
|
|
45
48
|
return { ...scope, contents };
|
|
46
49
|
}
|
|
@@ -51,48 +54,83 @@ export function createAgentsHouseAdapter(options) {
|
|
|
51
54
|
const notifications = houseNotifications(transport, 'credential');
|
|
52
55
|
const tracked = new Map();
|
|
53
56
|
const handed = new Map();
|
|
57
|
+
let heldRevisions;
|
|
54
58
|
let bounds;
|
|
55
59
|
function declared() {
|
|
56
60
|
if (bounds === undefined)
|
|
57
61
|
throw new Error('no Discovery declared the bounds');
|
|
58
62
|
return bounds;
|
|
59
63
|
}
|
|
60
|
-
async function
|
|
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) {
|
|
61
99
|
const { entry } = asked;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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);
|
|
65
107
|
if (scope === null) {
|
|
66
108
|
return asked.held === undefined
|
|
67
109
|
? UNCERTAIN_READ
|
|
68
|
-
:
|
|
69
|
-
? bootstrapTogether({ entry, held: undefined })
|
|
70
|
-
: continuedBootstrap({ entry, held: undefined }, continued);
|
|
110
|
+
: bootstrapTogether({ entry, held: undefined });
|
|
71
111
|
}
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
if (continued ===
|
|
75
|
-
handed.set(entry.locator,
|
|
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));
|
|
76
116
|
else
|
|
77
117
|
handed.delete(entry.locator);
|
|
78
118
|
notifications.reach(entry.authority, scope);
|
|
79
119
|
const seen = [
|
|
80
|
-
...
|
|
81
|
-
...
|
|
120
|
+
...continued.seen,
|
|
121
|
+
...(scope.files.some((file) => file.path === entry.schema) ? [entry.schema] : []),
|
|
82
122
|
];
|
|
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 }) };
|
|
123
|
+
const models = await partModels(scope, entry, new Set(seen), !more);
|
|
87
124
|
return {
|
|
88
|
-
...
|
|
125
|
+
...(await stepped(bootstrapping(scope))),
|
|
89
126
|
...(models === undefined ? {} : { models }),
|
|
90
|
-
...(
|
|
127
|
+
...(more ? { pageToken: JSON.stringify({ ...continued, next, seen }) } : {}),
|
|
128
|
+
...(sent.frame === null ? {} : { received: sent.frame }),
|
|
91
129
|
};
|
|
92
130
|
}
|
|
93
131
|
async function enumeratedPage(ask, page) {
|
|
94
132
|
const answer = await transport.door(DOOR_ENUMERATE, {
|
|
95
|
-
prefixes:
|
|
133
|
+
prefixes: [authorityRoot(ask.entry.handle)],
|
|
96
134
|
positions: [
|
|
97
135
|
{
|
|
98
136
|
...presented(ask.entry.authority, { position: page.after, logEpoch: ask.from.logEpoch }),
|
|
@@ -116,52 +154,54 @@ export function createAgentsHouseAdapter(options) {
|
|
|
116
154
|
if ('kind' in view)
|
|
117
155
|
return view;
|
|
118
156
|
notifications.reach(ask.entry.authority, view);
|
|
119
|
-
const answer = refreshFrom(view, ask.fromRoomVersion);
|
|
157
|
+
const answer = await refreshFrom(view, ask.fromRoomVersion);
|
|
120
158
|
if (answer.kind !== 'net-advancement')
|
|
121
159
|
return answer;
|
|
122
|
-
handed.set(ask.entry.locator,
|
|
160
|
+
handed.set(ask.entry.locator, roomVersion(view));
|
|
123
161
|
const models = await viewModels(view, ask.entry);
|
|
124
162
|
return models === undefined ? answer : { ...answer, models };
|
|
125
163
|
}
|
|
126
164
|
async function streamed(entry, request, from) {
|
|
127
|
-
|
|
128
|
-
if (held?.roomVersion !== request.fromRoomVersion)
|
|
165
|
+
if (handed.get(entry.locator) !== request.fromRoomVersion)
|
|
129
166
|
return null;
|
|
130
|
-
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));
|
|
131
172
|
if (advanced === null)
|
|
132
173
|
return null;
|
|
133
|
-
handed.set(entry.locator,
|
|
174
|
+
handed.set(entry.locator, advanced.page.roomVersion);
|
|
134
175
|
const models = await streamedModels(advanced.schemas, entry, 'credential');
|
|
135
176
|
return models === undefined ? advanced.page : { ...advanced.page, models };
|
|
136
177
|
}
|
|
137
|
-
function held(rooms
|
|
178
|
+
function held(rooms) {
|
|
138
179
|
const kept = rooms.flatMap((room) => {
|
|
139
180
|
const entry = tracked.get(room.locator);
|
|
140
181
|
const at = logPosition(room.roomVersion);
|
|
141
182
|
if (entry === undefined || at === null)
|
|
142
183
|
return [];
|
|
143
|
-
handed.set(room.locator,
|
|
184
|
+
handed.set(room.locator, room.roomVersion);
|
|
144
185
|
notifications.reach(entry.authority, at);
|
|
145
186
|
return [presented(entry.authority, at)];
|
|
146
187
|
});
|
|
147
|
-
notifications.send(kept
|
|
148
|
-
? null
|
|
149
|
-
: {
|
|
150
|
-
rooms: demand.rooms.flatMap((locator) => {
|
|
151
|
-
const entry = tracked.get(locator);
|
|
152
|
-
return entry === undefined ? [] : [entry.authority];
|
|
153
|
-
}),
|
|
154
|
-
conversations: demand.conversations,
|
|
155
|
-
});
|
|
188
|
+
notifications.send(kept);
|
|
156
189
|
}
|
|
157
|
-
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) {
|
|
158
197
|
if (outcome.kind !== 'accepted')
|
|
159
198
|
return outcome;
|
|
160
199
|
const held = handed.get(entry.locator);
|
|
161
200
|
if (held === undefined)
|
|
162
201
|
return outcome;
|
|
163
|
-
const follows = followsVersion(held
|
|
164
|
-
|
|
202
|
+
const follows = followsVersion(held, outcome.roomVersion);
|
|
203
|
+
if (follows)
|
|
204
|
+
handed.set(entry.locator, outcome.roomVersion);
|
|
165
205
|
const at = logPosition(outcome.roomVersion);
|
|
166
206
|
if (follows && at !== null)
|
|
167
207
|
notifications.reach(entry.authority, at);
|
|
@@ -184,68 +224,61 @@ export function createAgentsHouseAdapter(options) {
|
|
|
184
224
|
return result;
|
|
185
225
|
return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
|
|
186
226
|
}
|
|
187
|
-
function
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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;
|
|
193
238
|
}
|
|
194
239
|
async function bootstrapped(asks, answer) {
|
|
195
240
|
let open = asks.map((asked, ask) => ({ entry: asked.entry, asked, ask }));
|
|
196
241
|
while (open.length > 0) {
|
|
197
242
|
const unreached = [];
|
|
198
|
-
for (const part of selections(open,
|
|
199
|
-
const held = holding(part.map(({ asked }) => asked)
|
|
243
|
+
for (const part of selections(open, declared().max_prefixes)) {
|
|
244
|
+
const held = await holding(part.map(({ asked }) => asked));
|
|
200
245
|
const answered = await transport.door(DOOR_BOOTSTRAP, {
|
|
201
|
-
prefixes: part.
|
|
246
|
+
prefixes: part.map(({ entry }) => authorityRoot(entry.handle)),
|
|
202
247
|
...(held.length === 0 ? {} : { held }),
|
|
203
248
|
});
|
|
204
249
|
const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
|
|
205
|
-
const
|
|
250
|
+
const parted = classified[0] === null ? parseParted(answered.body) : null;
|
|
206
251
|
for (const [index, asked] of part.entries()) {
|
|
207
252
|
const failure = classified[index];
|
|
208
253
|
if (failure !== null && failure !== undefined) {
|
|
209
254
|
answer(asked.ask, failure);
|
|
210
255
|
continue;
|
|
211
256
|
}
|
|
212
|
-
if (
|
|
257
|
+
if (parted === null) {
|
|
213
258
|
answer(asked.ask, UNCERTAIN_READ);
|
|
214
259
|
continue;
|
|
215
260
|
}
|
|
216
|
-
const
|
|
217
|
-
if (
|
|
261
|
+
const room = parted.rooms.find((candidate) => candidate.anchor === asked.entry.authority);
|
|
262
|
+
if (room === undefined && parted.next !== undefined) {
|
|
218
263
|
unreached.push(asked);
|
|
219
264
|
continue;
|
|
220
265
|
}
|
|
221
|
-
|
|
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
|
+
}));
|
|
222
277
|
}
|
|
223
278
|
}
|
|
224
279
|
open = unreached;
|
|
225
280
|
}
|
|
226
281
|
}
|
|
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
282
|
function layered(asks) {
|
|
250
283
|
const depths = new Map();
|
|
251
284
|
const layers = [];
|
|
@@ -258,12 +291,12 @@ export function createAgentsHouseAdapter(options) {
|
|
|
258
291
|
}
|
|
259
292
|
async function enumerated(asks) {
|
|
260
293
|
const answers = [];
|
|
261
|
-
const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer,
|
|
294
|
+
const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, declared().max_prefixes));
|
|
262
295
|
for (const part of parts) {
|
|
263
296
|
let open = [...part];
|
|
264
297
|
while (open.length > 0) {
|
|
265
298
|
const answer = await transport.door(DOOR_ENUMERATE, {
|
|
266
|
-
prefixes: open.
|
|
299
|
+
prefixes: open.map(({ ask }) => authorityRoot(ask.entry.handle)),
|
|
267
300
|
positions: open.map(({ ask }) => presented(ask.entry.authority, ask.from)),
|
|
268
301
|
});
|
|
269
302
|
const classified = open.map(({ ask }) => classifyRead(answer.status, answer.body, ask.entry.locator));
|
|
@@ -302,10 +335,12 @@ export function createAgentsHouseAdapter(options) {
|
|
|
302
335
|
}
|
|
303
336
|
const minted = await transport.door(DOOR_BATCH_UPLOAD, { ...head, paths, bytes, sha256 });
|
|
304
337
|
if (minted.status !== 200) {
|
|
305
|
-
return {
|
|
338
|
+
return {
|
|
339
|
+
outcome: await stepped(mutationOutcome(minted.status, minted.body, entry.locator, entry.handle)),
|
|
340
|
+
};
|
|
306
341
|
}
|
|
307
342
|
const grant = parseGrant(minted.body);
|
|
308
|
-
if (grant === null
|
|
343
|
+
if (grant === null)
|
|
309
344
|
return { outcome: UNCERTAIN };
|
|
310
345
|
const delivered = await transport.transfer({
|
|
311
346
|
act: 'authored_batch_upload',
|
|
@@ -315,36 +350,31 @@ export function createAgentsHouseAdapter(options) {
|
|
|
315
350
|
if (delivered.status !== 200)
|
|
316
351
|
return { outcome: UNCERTAIN };
|
|
317
352
|
const holding = parseGrant(delivered.body);
|
|
318
|
-
if (holding === null
|
|
353
|
+
if (holding === null)
|
|
319
354
|
return { outcome: UNCERTAIN };
|
|
320
355
|
const upload = { url: holding.url, operation: holding.operation, bytes, sha256 };
|
|
321
356
|
await keep(upload);
|
|
322
357
|
return { body: { ...head, paths, upload, runtime: notifications.runtime } };
|
|
323
358
|
}
|
|
324
|
-
async function staged(answer, batch) {
|
|
359
|
+
async function staged(answer, { changeBatch: batch, file }) {
|
|
325
360
|
const refusal = doorRefusal(answer.body);
|
|
326
361
|
if (refusal?.code !== 'files_unstaged')
|
|
327
362
|
return null;
|
|
328
|
-
const
|
|
329
|
-
if (!Array.isArray(
|
|
363
|
+
const uploads = refusal.fields.upload;
|
|
364
|
+
if (!Array.isArray(uploads))
|
|
330
365
|
return false;
|
|
331
|
-
for (const
|
|
332
|
-
const
|
|
333
|
-
const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === sha256);
|
|
334
|
-
if (
|
|
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)
|
|
335
370
|
return false;
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
url,
|
|
339
|
-
body: change.blob,
|
|
340
|
-
operation,
|
|
341
|
-
});
|
|
342
|
-
if (delivered.status !== 200)
|
|
371
|
+
const finished = await finishedUpload(transport, upload, await file(change.localId));
|
|
372
|
+
if (finished?.body?.staged !== true)
|
|
343
373
|
return false;
|
|
344
374
|
}
|
|
345
375
|
return true;
|
|
346
376
|
}
|
|
347
|
-
async function submit(
|
|
377
|
+
async function submit(request, head, changes, delta, bytes, entry, handed, keep, into) {
|
|
348
378
|
const inline = { ...head, changes, runtime: notifications.runtime };
|
|
349
379
|
const prepared = uploadable(inline, bytes, declared())
|
|
350
380
|
? await uploaded(head, changes, delta, bytes, entry, handed, keep)
|
|
@@ -352,15 +382,20 @@ export function createAgentsHouseAdapter(options) {
|
|
|
352
382
|
if ('outcome' in prepared)
|
|
353
383
|
return prepared.outcome;
|
|
354
384
|
const answer = await transport.door(DOOR_BATCH, prepared.body);
|
|
355
|
-
const stagedFiles = await staged(answer,
|
|
385
|
+
const stagedFiles = await staged(answer, request);
|
|
356
386
|
if (stagedFiles === false)
|
|
357
387
|
return UNCERTAIN;
|
|
358
388
|
const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
|
|
359
|
-
if (stagedFiles === true && (await staged(settled,
|
|
389
|
+
if (stagedFiles === true && (await staged(settled, request)) !== null)
|
|
360
390
|
return UNCERTAIN;
|
|
361
|
-
return mutationOutcome(settled.status, settled.body, entry.locator, entry.handle
|
|
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
|
+
}));
|
|
362
397
|
}
|
|
363
|
-
|
|
398
|
+
function addressed(answer, locator) {
|
|
364
399
|
const failure = classifyDoor(answer.status, answer.body, locator);
|
|
365
400
|
if (failure !== null) {
|
|
366
401
|
return failure.kind === 'scoped-refusal'
|
|
@@ -372,20 +407,69 @@ export function createAgentsHouseAdapter(options) {
|
|
|
372
407
|
}
|
|
373
408
|
: failure;
|
|
374
409
|
}
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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)
|
|
380
444
|
return UNCERTAIN_READ;
|
|
381
|
-
return {
|
|
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
|
+
});
|
|
382
461
|
}
|
|
383
462
|
return {
|
|
463
|
+
roomTypes: HOUSE_ROOM_TYPES,
|
|
464
|
+
nameableRoomTypes: HOUSE_NAMEABLE_ROOM_TYPES,
|
|
384
465
|
async authorize() {
|
|
385
466
|
return options.authorize();
|
|
386
467
|
},
|
|
387
|
-
async discover(_authorization) {
|
|
388
|
-
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
|
+
});
|
|
389
473
|
const failed = discoveryFailure(answer.status, answer.body);
|
|
390
474
|
if (failed !== null)
|
|
391
475
|
return failed;
|
|
@@ -394,16 +478,31 @@ export function createAgentsHouseAdapter(options) {
|
|
|
394
478
|
return { kind: 'transport-uncertainty' };
|
|
395
479
|
bounds = document.bounds;
|
|
396
480
|
const entries = trackedFrom(document);
|
|
397
|
-
|
|
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);
|
|
398
496
|
for (const entry of entries)
|
|
399
497
|
tracked.set(entry.locator, entry);
|
|
400
498
|
for (const locator of handed.keys())
|
|
401
499
|
if (!tracked.has(locator))
|
|
402
500
|
handed.delete(locator);
|
|
403
|
-
notifications.track(
|
|
501
|
+
notifications.track([...tracked.values()]);
|
|
404
502
|
return {
|
|
405
|
-
...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document)),
|
|
503
|
+
...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions, [...tracked.values()])),
|
|
406
504
|
member: document.member,
|
|
505
|
+
...(covered === null ? {} : { covered }),
|
|
407
506
|
};
|
|
408
507
|
},
|
|
409
508
|
async bootstrap(_authorization, request) {
|
|
@@ -412,7 +511,7 @@ export function createAgentsHouseAdapter(options) {
|
|
|
412
511
|
return { kind: 'room-not-readable', locator: request.locator };
|
|
413
512
|
const asked = { entry, held: request.held };
|
|
414
513
|
if (request.pageToken !== undefined) {
|
|
415
|
-
return
|
|
514
|
+
return partPage(asked, JSON.parse(request.pageToken), request.received, request.arriving);
|
|
416
515
|
}
|
|
417
516
|
return bootstrapTogether(asked);
|
|
418
517
|
},
|
|
@@ -434,18 +533,27 @@ export function createAgentsHouseAdapter(options) {
|
|
|
434
533
|
const entry = authorityFor(request.locator);
|
|
435
534
|
if (entry === undefined)
|
|
436
535
|
return { kind: 'room-not-readable', locator: request.locator };
|
|
437
|
-
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);
|
|
438
545
|
if (mapped.kind === 'unmapped')
|
|
439
546
|
return rejectedMapping(mapped);
|
|
440
|
-
const head = operationHead(
|
|
547
|
+
const head = operationHead(batch);
|
|
441
548
|
const delta = deltaBody(mapped.changes);
|
|
442
549
|
const bytes = deltaBytes(delta);
|
|
443
550
|
const handed = request.upload;
|
|
444
|
-
const
|
|
445
|
-
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);
|
|
446
554
|
if (handed === undefined || !lapsedGrant(outcome))
|
|
447
|
-
return
|
|
448
|
-
return
|
|
555
|
+
return settle(outcome);
|
|
556
|
+
return settle(await answer(undefined));
|
|
449
557
|
},
|
|
450
558
|
async history(_authorization, request) {
|
|
451
559
|
const entry = authorityFor(request.locator);
|
|
@@ -463,35 +571,45 @@ export function createAgentsHouseAdapter(options) {
|
|
|
463
571
|
});
|
|
464
572
|
},
|
|
465
573
|
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);
|
|
574
|
+
const ask = pathAddress(request);
|
|
575
|
+
if (ask === null)
|
|
576
|
+
return { kind: 'room-not-readable', locator: request.locator };
|
|
577
|
+
return downloaded(ask, request.locator);
|
|
486
578
|
},
|
|
487
|
-
|
|
488
|
-
const
|
|
489
|
-
if (
|
|
490
|
-
return
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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;
|
|
495
613
|
},
|
|
496
614
|
reached(locator, version, target) {
|
|
497
615
|
return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
|
|
@@ -505,7 +623,10 @@ export function createAgentsHouseAdapter(options) {
|
|
|
505
623
|
roomStream: {
|
|
506
624
|
hold: held,
|
|
507
625
|
reopen: notifications.reopen,
|
|
508
|
-
observe
|
|
626
|
+
observe(notify, receipts, revisions) {
|
|
627
|
+
heldRevisions = revisions;
|
|
628
|
+
return notifications.observe(notify, receipts);
|
|
629
|
+
},
|
|
509
630
|
published(locator, roomVersion) {
|
|
510
631
|
const at = logPosition(roomVersion);
|
|
511
632
|
if (at !== null)
|