@agentshouse/mdruntime-adapter 0.1.0-ca5b2f7fcb9d1 → 0.1.0-ca632988146a3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/actions.d.ts +11 -23
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +26 -137
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +239 -199
- 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 +38 -34
- package/dist/gather.d.ts +1 -2
- package/dist/gather.d.ts.map +1 -1
- package/dist/gather.js +5 -15
- package/dist/house-model.d.ts +2 -0
- package/dist/house-model.d.ts.map +1 -0
- package/dist/house-model.js +491 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -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 +14 -0
- package/dist/part.d.ts.map +1 -0
- package/dist/part.js +90 -0
- package/dist/read.d.ts +54 -25
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js +264 -150
- package/dist/room-stream.d.ts +19 -0
- package/dist/room-stream.d.ts.map +1 -0
- package/dist/room-stream.js +57 -0
- package/dist/room-types.d.ts +7 -0
- package/dist/room-types.d.ts.map +1 -0
- package/dist/room-types.js +126 -0
- package/dist/site.d.ts.map +1 -1
- package/dist/site.js +66 -114
- package/dist/stream.d.ts +3 -3
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +71 -40
- package/dist/transport.d.ts +39 -9
- 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 +16 -1
- package/dist/write.d.ts.map +1 -1
- package/dist/write.js +101 -55
- package/package.json +8 -4
- package/AGENTS.md +0 -296
- 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 {
|
|
3
|
-
import { gathered,
|
|
3
|
+
import { streamedModels, viewModels } from './model.js';
|
|
4
|
+
import { gathered, selections } from './gather.js';
|
|
4
5
|
import { createSiteAdapter } from './site.js';
|
|
5
|
-
import {
|
|
6
|
+
import { authorityRoot, bootstrapping, parseEnumeration, parseFileAddress, parseGrant, refreshFrom, viewOf, wholeView, } from './read.js';
|
|
7
|
+
import { roomStreams } from './room-stream.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,52 @@ export function createAgentsHouseAdapter(options) {
|
|
|
51
54
|
const notifications = houseNotifications(transport, 'credential');
|
|
52
55
|
const tracked = new Map();
|
|
53
56
|
const handed = new Map();
|
|
57
|
+
const rooms = roomStreams('credential');
|
|
58
|
+
const unreused = new Set();
|
|
59
|
+
let heldRevisions;
|
|
54
60
|
let bounds;
|
|
55
61
|
function declared() {
|
|
56
62
|
if (bounds === undefined)
|
|
57
63
|
throw new Error('no Discovery declared the bounds');
|
|
58
64
|
return bounds;
|
|
59
65
|
}
|
|
60
|
-
async function
|
|
61
|
-
const
|
|
62
|
-
if (
|
|
63
|
-
return
|
|
64
|
-
const scope = await reused(
|
|
66
|
+
async function streamedPage(entry, token, held) {
|
|
67
|
+
const part = await rooms.part(entry, token);
|
|
68
|
+
if ('kind' in part)
|
|
69
|
+
return part;
|
|
70
|
+
const scope = await reused(part.scope, held);
|
|
65
71
|
if (scope === null) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
: continued === null
|
|
69
|
-
? bootstrapTogether({ entry, held: undefined })
|
|
70
|
-
: continuedBootstrap({ entry, held: undefined }, continued);
|
|
72
|
+
unreused.add(entry.locator);
|
|
73
|
+
return UNCERTAIN_READ;
|
|
71
74
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (continued === null && next === null)
|
|
75
|
-
handed.set(entry.locator, scopeHanded(scope));
|
|
75
|
+
if (part.first && part.ended)
|
|
76
|
+
handed.set(entry.locator, roomVersion(scope));
|
|
76
77
|
else
|
|
77
78
|
handed.delete(entry.locator);
|
|
78
79
|
notifications.reach(entry.authority, scope);
|
|
79
|
-
const seen = [
|
|
80
|
-
...(continued?.seen ?? []),
|
|
81
|
-
...entry.schemas.filter((schema) => scope.files.some((file) => file.path === schema)),
|
|
82
|
-
];
|
|
83
|
-
const models = await partModels(scope, entry, new Set(seen), next === null);
|
|
84
|
-
const continuation = next === null
|
|
85
|
-
? null
|
|
86
|
-
: { after: next, seen, ...(cursor?.cut === undefined ? {} : { cut: cursor.cut }) };
|
|
87
80
|
return {
|
|
88
|
-
...
|
|
89
|
-
...(models === undefined ? {} : { models }),
|
|
90
|
-
...(
|
|
81
|
+
...(await stepped(bootstrapping(scope))),
|
|
82
|
+
...(part.models === undefined ? {} : { models: part.models }),
|
|
83
|
+
...(part.pageToken === undefined ? {} : { pageToken: part.pageToken }),
|
|
84
|
+
bytes: part.bytes,
|
|
91
85
|
};
|
|
92
86
|
}
|
|
87
|
+
async function openedRoom(entry, request) {
|
|
88
|
+
const reusing = unreused.delete(entry.locator) ? undefined : request.held;
|
|
89
|
+
const files = reusing === undefined
|
|
90
|
+
? null
|
|
91
|
+
: await heldFiles(entry.authority, reusing, declared().max_held_files);
|
|
92
|
+
const answered = await transport.streamed(DOOR_BOOTSTRAP, {
|
|
93
|
+
prefixes: [authorityRoot(entry.handle)],
|
|
94
|
+
...(files === null ? {} : { held: [files] }),
|
|
95
|
+
}, request.signal ?? new AbortController().signal);
|
|
96
|
+
if (answered.room === null)
|
|
97
|
+
return classifyDoor(answered.status, answered.body, entry.locator) ?? UNCERTAIN_READ;
|
|
98
|
+
return rooms.head(entry, answered.room, request.arriving, request.signal);
|
|
99
|
+
}
|
|
93
100
|
async function enumeratedPage(ask, page) {
|
|
94
101
|
const answer = await transport.door(DOOR_ENUMERATE, {
|
|
95
|
-
prefixes:
|
|
102
|
+
prefixes: [authorityRoot(ask.entry.handle)],
|
|
96
103
|
positions: [
|
|
97
104
|
{
|
|
98
105
|
...presented(ask.entry.authority, { position: page.after, logEpoch: ask.from.logEpoch }),
|
|
@@ -116,21 +123,24 @@ export function createAgentsHouseAdapter(options) {
|
|
|
116
123
|
if ('kind' in view)
|
|
117
124
|
return view;
|
|
118
125
|
notifications.reach(ask.entry.authority, view);
|
|
119
|
-
const answer = refreshFrom(view, ask.fromRoomVersion);
|
|
126
|
+
const answer = await refreshFrom(view, ask.fromRoomVersion);
|
|
120
127
|
if (answer.kind !== 'net-advancement')
|
|
121
128
|
return answer;
|
|
122
|
-
handed.set(ask.entry.locator,
|
|
129
|
+
handed.set(ask.entry.locator, roomVersion(view));
|
|
123
130
|
const models = await viewModels(view, ask.entry);
|
|
124
131
|
return models === undefined ? answer : { ...answer, models };
|
|
125
132
|
}
|
|
126
133
|
async function streamed(entry, request, from) {
|
|
127
|
-
|
|
128
|
-
if (held?.roomVersion !== request.fromRoomVersion)
|
|
134
|
+
if (handed.get(entry.locator) !== request.fromRoomVersion)
|
|
129
135
|
return null;
|
|
130
|
-
const
|
|
136
|
+
const asked = notifications.edited(entry, from);
|
|
137
|
+
const kept = asked.length === 0 || heldRevisions === undefined
|
|
138
|
+
? new Map()
|
|
139
|
+
: await heldRevisions(entry.locator, asked);
|
|
140
|
+
const advanced = await stepped(notifications.advance(entry, from, (path, revision) => kept.get(path) === revision));
|
|
131
141
|
if (advanced === null)
|
|
132
142
|
return null;
|
|
133
|
-
handed.set(entry.locator,
|
|
143
|
+
handed.set(entry.locator, advanced.page.roomVersion);
|
|
134
144
|
const models = await streamedModels(advanced.schemas, entry, 'credential');
|
|
135
145
|
return models === undefined ? advanced.page : { ...advanced.page, models };
|
|
136
146
|
}
|
|
@@ -140,20 +150,27 @@ export function createAgentsHouseAdapter(options) {
|
|
|
140
150
|
const at = logPosition(room.roomVersion);
|
|
141
151
|
if (entry === undefined || at === null)
|
|
142
152
|
return [];
|
|
143
|
-
handed.set(room.locator,
|
|
153
|
+
handed.set(room.locator, room.roomVersion);
|
|
144
154
|
notifications.reach(entry.authority, at);
|
|
145
155
|
return [presented(entry.authority, at)];
|
|
146
156
|
});
|
|
147
157
|
notifications.send(kept);
|
|
148
158
|
}
|
|
149
|
-
function
|
|
159
|
+
async function adoptedMove(entry, into, outcome) {
|
|
160
|
+
if (outcome.kind === 'accepted' && outcome.destination !== undefined) {
|
|
161
|
+
await adopted(into, { kind: 'accepted', ...outcome.destination });
|
|
162
|
+
}
|
|
163
|
+
return adopted(entry, outcome);
|
|
164
|
+
}
|
|
165
|
+
async function adopted(entry, outcome) {
|
|
150
166
|
if (outcome.kind !== 'accepted')
|
|
151
167
|
return outcome;
|
|
152
168
|
const held = handed.get(entry.locator);
|
|
153
169
|
if (held === undefined)
|
|
154
170
|
return outcome;
|
|
155
|
-
const follows = followsVersion(held
|
|
156
|
-
|
|
171
|
+
const follows = followsVersion(held, outcome.roomVersion);
|
|
172
|
+
if (follows)
|
|
173
|
+
handed.set(entry.locator, outcome.roomVersion);
|
|
157
174
|
const at = logPosition(outcome.roomVersion);
|
|
158
175
|
if (follows && at !== null)
|
|
159
176
|
notifications.reach(entry.authority, at);
|
|
@@ -176,68 +193,6 @@ export function createAgentsHouseAdapter(options) {
|
|
|
176
193
|
return result;
|
|
177
194
|
return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
|
|
178
195
|
}
|
|
179
|
-
function weight(entry) {
|
|
180
|
-
return contentPrefixes(entry).length;
|
|
181
|
-
}
|
|
182
|
-
function holding(asks, after) {
|
|
183
|
-
const budget = Math.floor(declared().max_held_files / Math.max(1, asks.filter((ask) => ask.held !== undefined).length));
|
|
184
|
-
return asks.flatMap((ask) => heldFiles(ask.entry.authority, ask.held, after, budget) ?? []);
|
|
185
|
-
}
|
|
186
|
-
async function bootstrapped(asks, answer) {
|
|
187
|
-
let open = asks.map((asked, ask) => ({ entry: asked.entry, asked, ask }));
|
|
188
|
-
while (open.length > 0) {
|
|
189
|
-
const unreached = [];
|
|
190
|
-
for (const part of selections(open, ({ entry }) => weight(entry), declared().max_prefixes)) {
|
|
191
|
-
const held = holding(part.map(({ asked }) => asked), null);
|
|
192
|
-
const answered = await transport.door(DOOR_BOOTSTRAP, {
|
|
193
|
-
prefixes: part.flatMap(({ entry }) => contentPrefixes(entry)),
|
|
194
|
-
...(held.length === 0 ? {} : { held }),
|
|
195
|
-
});
|
|
196
|
-
const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
|
|
197
|
-
const envelope = classified[0] === null ? parseEnvelope(answered.body) : null;
|
|
198
|
-
for (const [index, asked] of part.entries()) {
|
|
199
|
-
const failure = classified[index];
|
|
200
|
-
if (failure !== null && failure !== undefined) {
|
|
201
|
-
answer(asked.ask, failure);
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
if (envelope === null) {
|
|
205
|
-
answer(asked.ask, UNCERTAIN_READ);
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
const scope = envelope.scopes.find((candidate) => matchesScope(candidate, asked.entry));
|
|
209
|
-
if (scope === undefined && envelope.next !== undefined) {
|
|
210
|
-
unreached.push(asked);
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
answer(asked.ask, await bootstrappedRoom(envelope, scope, asked.asked, null));
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
open = unreached;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
async function continuedBootstrap(asked, continued) {
|
|
220
|
-
const { entry } = asked;
|
|
221
|
-
const after = {
|
|
222
|
-
room: entry.authority,
|
|
223
|
-
path: continued.after,
|
|
224
|
-
...(continued.cut === undefined ? {} : { cut: continued.cut }),
|
|
225
|
-
};
|
|
226
|
-
const held = holding([asked], continued.after);
|
|
227
|
-
const answered = await transport.door(DOOR_BOOTSTRAP, {
|
|
228
|
-
prefixes: contentPrefixes(entry),
|
|
229
|
-
after,
|
|
230
|
-
...(held.length === 0 ? {} : { held }),
|
|
231
|
-
});
|
|
232
|
-
const failure = classifyRead(answered.status, answered.body, entry.locator);
|
|
233
|
-
if (failure !== null)
|
|
234
|
-
return failure;
|
|
235
|
-
const envelope = parseEnvelope(answered.body);
|
|
236
|
-
if (envelope === null)
|
|
237
|
-
return UNCERTAIN_READ;
|
|
238
|
-
const scope = envelope.scopes.find((candidate) => matchesScope(candidate, entry));
|
|
239
|
-
return bootstrappedRoom(envelope, scope, asked, continued);
|
|
240
|
-
}
|
|
241
196
|
function layered(asks) {
|
|
242
197
|
const depths = new Map();
|
|
243
198
|
const layers = [];
|
|
@@ -250,12 +205,12 @@ export function createAgentsHouseAdapter(options) {
|
|
|
250
205
|
}
|
|
251
206
|
async function enumerated(asks) {
|
|
252
207
|
const answers = [];
|
|
253
|
-
const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer,
|
|
208
|
+
const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, declared().max_prefixes));
|
|
254
209
|
for (const part of parts) {
|
|
255
210
|
let open = [...part];
|
|
256
211
|
while (open.length > 0) {
|
|
257
212
|
const answer = await transport.door(DOOR_ENUMERATE, {
|
|
258
|
-
prefixes: open.
|
|
213
|
+
prefixes: open.map(({ ask }) => authorityRoot(ask.entry.handle)),
|
|
259
214
|
positions: open.map(({ ask }) => presented(ask.entry.authority, ask.from)),
|
|
260
215
|
});
|
|
261
216
|
const classified = open.map(({ ask }) => classifyRead(answer.status, answer.body, ask.entry.locator));
|
|
@@ -284,7 +239,6 @@ export function createAgentsHouseAdapter(options) {
|
|
|
284
239
|
}
|
|
285
240
|
return answers;
|
|
286
241
|
}
|
|
287
|
-
const bootstrapTogether = gatheredEach(bootstrapped);
|
|
288
242
|
const enumerateTogether = gathered(enumerated);
|
|
289
243
|
async function uploaded(head, changes, delta, bytes, entry, handed, keep) {
|
|
290
244
|
const paths = batchPaths(changes);
|
|
@@ -294,10 +248,12 @@ export function createAgentsHouseAdapter(options) {
|
|
|
294
248
|
}
|
|
295
249
|
const minted = await transport.door(DOOR_BATCH_UPLOAD, { ...head, paths, bytes, sha256 });
|
|
296
250
|
if (minted.status !== 200) {
|
|
297
|
-
return {
|
|
251
|
+
return {
|
|
252
|
+
outcome: await stepped(mutationOutcome(minted.status, minted.body, entry.locator, entry.handle)),
|
|
253
|
+
};
|
|
298
254
|
}
|
|
299
255
|
const grant = parseGrant(minted.body);
|
|
300
|
-
if (grant === null
|
|
256
|
+
if (grant === null)
|
|
301
257
|
return { outcome: UNCERTAIN };
|
|
302
258
|
const delivered = await transport.transfer({
|
|
303
259
|
act: 'authored_batch_upload',
|
|
@@ -307,36 +263,31 @@ export function createAgentsHouseAdapter(options) {
|
|
|
307
263
|
if (delivered.status !== 200)
|
|
308
264
|
return { outcome: UNCERTAIN };
|
|
309
265
|
const holding = parseGrant(delivered.body);
|
|
310
|
-
if (holding === null
|
|
266
|
+
if (holding === null)
|
|
311
267
|
return { outcome: UNCERTAIN };
|
|
312
268
|
const upload = { url: holding.url, operation: holding.operation, bytes, sha256 };
|
|
313
269
|
await keep(upload);
|
|
314
270
|
return { body: { ...head, paths, upload, runtime: notifications.runtime } };
|
|
315
271
|
}
|
|
316
|
-
async function staged(answer, batch) {
|
|
272
|
+
async function staged(answer, { changeBatch: batch, file }) {
|
|
317
273
|
const refusal = doorRefusal(answer.body);
|
|
318
274
|
if (refusal?.code !== 'files_unstaged')
|
|
319
275
|
return null;
|
|
320
|
-
const
|
|
321
|
-
if (!Array.isArray(
|
|
276
|
+
const uploads = refusal.fields.upload;
|
|
277
|
+
if (!Array.isArray(uploads))
|
|
322
278
|
return false;
|
|
323
|
-
for (const
|
|
324
|
-
const
|
|
325
|
-
const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === sha256);
|
|
326
|
-
if (
|
|
279
|
+
for (const named of uploads) {
|
|
280
|
+
const upload = houseUpload(named);
|
|
281
|
+
const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === named.sha256);
|
|
282
|
+
if (upload === null || change === undefined)
|
|
327
283
|
return false;
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
url,
|
|
331
|
-
body: change.blob,
|
|
332
|
-
operation,
|
|
333
|
-
});
|
|
334
|
-
if (delivered.status !== 200)
|
|
284
|
+
const finished = await finishedUpload(transport, upload, await file(change.localId));
|
|
285
|
+
if (finished?.body?.staged !== true)
|
|
335
286
|
return false;
|
|
336
287
|
}
|
|
337
288
|
return true;
|
|
338
289
|
}
|
|
339
|
-
async function submit(
|
|
290
|
+
async function submit(request, head, changes, delta, bytes, entry, handed, keep, into) {
|
|
340
291
|
const inline = { ...head, changes, runtime: notifications.runtime };
|
|
341
292
|
const prepared = uploadable(inline, bytes, declared())
|
|
342
293
|
? await uploaded(head, changes, delta, bytes, entry, handed, keep)
|
|
@@ -344,15 +295,20 @@ export function createAgentsHouseAdapter(options) {
|
|
|
344
295
|
if ('outcome' in prepared)
|
|
345
296
|
return prepared.outcome;
|
|
346
297
|
const answer = await transport.door(DOOR_BATCH, prepared.body);
|
|
347
|
-
const stagedFiles = await staged(answer,
|
|
298
|
+
const stagedFiles = await staged(answer, request);
|
|
348
299
|
if (stagedFiles === false)
|
|
349
300
|
return UNCERTAIN;
|
|
350
301
|
const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
|
|
351
|
-
if (stagedFiles === true && (await staged(settled,
|
|
302
|
+
if (stagedFiles === true && (await staged(settled, request)) !== null)
|
|
352
303
|
return UNCERTAIN;
|
|
353
|
-
return mutationOutcome(settled.status, settled.body, entry.locator, entry.handle
|
|
304
|
+
return stepped(mutationOutcome(settled.status, settled.body, entry.locator, entry.handle, into === undefined
|
|
305
|
+
? undefined
|
|
306
|
+
: {
|
|
307
|
+
source: { authority: entry.authority, handle: entry.handle },
|
|
308
|
+
destination: { authority: into.authority, handle: into.handle },
|
|
309
|
+
}));
|
|
354
310
|
}
|
|
355
|
-
|
|
311
|
+
function addressed(answer, locator) {
|
|
356
312
|
const failure = classifyDoor(answer.status, answer.body, locator);
|
|
357
313
|
if (failure !== null) {
|
|
358
314
|
return failure.kind === 'scoped-refusal'
|
|
@@ -364,20 +320,69 @@ export function createAgentsHouseAdapter(options) {
|
|
|
364
320
|
}
|
|
365
321
|
: failure;
|
|
366
322
|
}
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
323
|
+
const address = parseFileAddress(answer.body);
|
|
324
|
+
return address === null ? UNCERTAIN_READ : { kind: 'address', ...address };
|
|
325
|
+
}
|
|
326
|
+
function linked(answer, locator) {
|
|
327
|
+
const address = addressed(answer, locator);
|
|
328
|
+
return address.kind === 'address'
|
|
329
|
+
? { kind: 'link', url: address.url, name: address.name, mediaType: address.mediaType }
|
|
330
|
+
: address;
|
|
331
|
+
}
|
|
332
|
+
function refusedAttach(answer, locator) {
|
|
333
|
+
const failure = classifyDoor(answer.status, answer.body, locator);
|
|
334
|
+
if (failure === null)
|
|
335
|
+
return null;
|
|
336
|
+
return failure.kind === 'scoped-refusal'
|
|
337
|
+
? {
|
|
338
|
+
kind: 'rejected',
|
|
339
|
+
code: failure.code,
|
|
340
|
+
fields: failure.fields,
|
|
341
|
+
retryable: failure.retryable,
|
|
342
|
+
}
|
|
343
|
+
: failure;
|
|
344
|
+
}
|
|
345
|
+
async function downloaded(ask, locator) {
|
|
346
|
+
let address = addressed(await ask(), locator);
|
|
347
|
+
if (address.kind !== 'address')
|
|
348
|
+
return address;
|
|
349
|
+
let read = await transport.download(address.url);
|
|
350
|
+
if (read.status === EXPIRED_ADDRESS) {
|
|
351
|
+
address = addressed(await ask(), locator);
|
|
352
|
+
if (address.kind !== 'address')
|
|
353
|
+
return address;
|
|
354
|
+
read = await transport.download(address.url);
|
|
355
|
+
}
|
|
356
|
+
if (read.status !== 200 || read.body === null)
|
|
372
357
|
return UNCERTAIN_READ;
|
|
373
|
-
return {
|
|
358
|
+
return {
|
|
359
|
+
kind: 'binary',
|
|
360
|
+
body: read.body,
|
|
361
|
+
mediaType: address.mediaType,
|
|
362
|
+
sha256: address.sha256,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function pathAddress(request) {
|
|
366
|
+
const entry = authorityFor(request.locator);
|
|
367
|
+
if (entry === undefined)
|
|
368
|
+
return null;
|
|
369
|
+
return () => transport.door(DOOR_FILES_GET, {
|
|
370
|
+
room_ref: entry.authority,
|
|
371
|
+
path: request.path,
|
|
372
|
+
revision: request.revision,
|
|
373
|
+
});
|
|
374
374
|
}
|
|
375
375
|
return {
|
|
376
|
+
roomTypes: HOUSE_ROOM_TYPES,
|
|
377
|
+
nameableRoomTypes: HOUSE_NAMEABLE_ROOM_TYPES,
|
|
376
378
|
async authorize() {
|
|
377
379
|
return options.authorize();
|
|
378
380
|
},
|
|
379
|
-
async discover(_authorization) {
|
|
380
|
-
const answer = await transport.door(DOOR_DISCOVER, {
|
|
381
|
+
async discover(_authorization, scope) {
|
|
382
|
+
const answer = await transport.door(DOOR_DISCOVER, {
|
|
383
|
+
prefixes: [...DISCOVER_PREFIXES],
|
|
384
|
+
...(scope === undefined ? {} : { rooms: scope.authorities }),
|
|
385
|
+
});
|
|
381
386
|
const failed = discoveryFailure(answer.status, answer.body);
|
|
382
387
|
if (failed !== null)
|
|
383
388
|
return failed;
|
|
@@ -386,27 +391,40 @@ export function createAgentsHouseAdapter(options) {
|
|
|
386
391
|
return { kind: 'transport-uncertainty' };
|
|
387
392
|
bounds = document.bounds;
|
|
388
393
|
const entries = trackedFrom(document);
|
|
389
|
-
|
|
394
|
+
const covered = scope === undefined
|
|
395
|
+
? null
|
|
396
|
+
: [
|
|
397
|
+
...new Set([
|
|
398
|
+
...[...tracked.values()]
|
|
399
|
+
.filter((entry) => scope.authorities.includes(entry.authority))
|
|
400
|
+
.map((entry) => entry.locator),
|
|
401
|
+
...entries.map((entry) => entry.locator),
|
|
402
|
+
]),
|
|
403
|
+
];
|
|
404
|
+
if (covered === null)
|
|
405
|
+
tracked.clear();
|
|
406
|
+
else
|
|
407
|
+
for (const locator of covered)
|
|
408
|
+
tracked.delete(locator);
|
|
390
409
|
for (const entry of entries)
|
|
391
410
|
tracked.set(entry.locator, entry);
|
|
392
411
|
for (const locator of handed.keys())
|
|
393
412
|
if (!tracked.has(locator))
|
|
394
413
|
handed.delete(locator);
|
|
395
|
-
notifications.track(
|
|
414
|
+
notifications.track([...tracked.values()]);
|
|
396
415
|
return {
|
|
397
|
-
...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document)),
|
|
416
|
+
...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions, [...tracked.values()])),
|
|
398
417
|
member: document.member,
|
|
418
|
+
...(covered === null ? {} : { covered }),
|
|
399
419
|
};
|
|
400
420
|
},
|
|
401
421
|
async bootstrap(_authorization, request) {
|
|
402
422
|
const entry = authorityFor(request.locator);
|
|
403
423
|
if (entry === undefined)
|
|
404
424
|
return { kind: 'room-not-readable', locator: request.locator };
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
return bootstrapTogether(asked);
|
|
425
|
+
return request.pageToken === undefined
|
|
426
|
+
? openedRoom(entry, request)
|
|
427
|
+
: streamedPage(entry, request.pageToken, request.held);
|
|
410
428
|
},
|
|
411
429
|
async refresh(_authorization, request) {
|
|
412
430
|
const entry = authorityFor(request.locator);
|
|
@@ -426,18 +444,27 @@ export function createAgentsHouseAdapter(options) {
|
|
|
426
444
|
const entry = authorityFor(request.locator);
|
|
427
445
|
if (entry === undefined)
|
|
428
446
|
return { kind: 'room-not-readable', locator: request.locator };
|
|
429
|
-
const
|
|
447
|
+
const batch = request.changeBatch;
|
|
448
|
+
const destination = batch.between?.destination.room;
|
|
449
|
+
const into = destination === undefined ? undefined : authorityFor(destination);
|
|
450
|
+
if (destination !== undefined && into === undefined) {
|
|
451
|
+
return { kind: 'room-not-readable', locator: destination };
|
|
452
|
+
}
|
|
453
|
+
const mapped = into === undefined
|
|
454
|
+
? houseChanges(batch, entry.handle)
|
|
455
|
+
: houseMove(batch, entry.handle, into.handle);
|
|
430
456
|
if (mapped.kind === 'unmapped')
|
|
431
457
|
return rejectedMapping(mapped);
|
|
432
|
-
const head = operationHead(
|
|
458
|
+
const head = operationHead(batch);
|
|
433
459
|
const delta = deltaBody(mapped.changes);
|
|
434
460
|
const bytes = deltaBytes(delta);
|
|
435
461
|
const handed = request.upload;
|
|
436
|
-
const
|
|
437
|
-
const
|
|
462
|
+
const answer = (upload) => submit(request, head, mapped.changes, delta, bytes, entry, upload, keep, into);
|
|
463
|
+
const settle = (outcome) => into === undefined ? adopted(entry, outcome) : adoptedMove(entry, into, outcome);
|
|
464
|
+
const outcome = await answer(handed);
|
|
438
465
|
if (handed === undefined || !lapsedGrant(outcome))
|
|
439
|
-
return
|
|
440
|
-
return
|
|
466
|
+
return settle(outcome);
|
|
467
|
+
return settle(await answer(undefined));
|
|
441
468
|
},
|
|
442
469
|
async history(_authorization, request) {
|
|
443
470
|
const entry = authorityFor(request.locator);
|
|
@@ -455,35 +482,45 @@ export function createAgentsHouseAdapter(options) {
|
|
|
455
482
|
});
|
|
456
483
|
},
|
|
457
484
|
async binary(_authorization, request) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
const referrer = houseReferrer(request.referrer, tracked);
|
|
470
|
-
if (referrer === null) {
|
|
471
|
-
return { kind: 'rejected', code: 'referrer_unknown', fields: {}, retryable: false };
|
|
472
|
-
}
|
|
473
|
-
const answer = await transport.door(DOOR_FILES_GET, {
|
|
474
|
-
file: request.fileRef,
|
|
475
|
-
referrer: referrer.referrer,
|
|
476
|
-
});
|
|
477
|
-
return downloaded(answer, referrer.locator);
|
|
485
|
+
const ask = pathAddress(request);
|
|
486
|
+
if (ask === null)
|
|
487
|
+
return { kind: 'room-not-readable', locator: request.locator };
|
|
488
|
+
return downloaded(ask, request.locator);
|
|
489
|
+
},
|
|
490
|
+
async link(_authorization, request) {
|
|
491
|
+
const ask = pathAddress(request);
|
|
492
|
+
if (ask === null)
|
|
493
|
+
return { kind: 'room-not-readable', locator: request.locator };
|
|
494
|
+
return linked(await ask(), request.locator);
|
|
478
495
|
},
|
|
479
|
-
|
|
480
|
-
const
|
|
481
|
-
if (
|
|
482
|
-
return
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
496
|
+
async attach(_authorization, request) {
|
|
497
|
+
const entry = authorityFor(request.locator);
|
|
498
|
+
if (entry === undefined)
|
|
499
|
+
return { kind: 'room-not-readable', locator: request.locator };
|
|
500
|
+
const declared = await transport.door(DOOR_FILES_ATTACH, {
|
|
501
|
+
room_ref: entry.authority,
|
|
502
|
+
name: request.name,
|
|
503
|
+
bytes: request.byteLength,
|
|
504
|
+
sha256: request.sha256,
|
|
505
|
+
media_type: request.mediaType.length > 0 ? request.mediaType : OCTET_STREAM,
|
|
506
|
+
});
|
|
507
|
+
const refused = refusedAttach(declared, request.locator);
|
|
508
|
+
if (refused !== null)
|
|
509
|
+
return refused;
|
|
510
|
+
const upload = houseUpload(declared.body?.upload);
|
|
511
|
+
if (upload === null)
|
|
512
|
+
return UNCERTAIN_READ;
|
|
513
|
+
const finished = await finishedUpload(transport, upload, request.blob);
|
|
514
|
+
if (finished === null)
|
|
515
|
+
return UNCERTAIN_READ;
|
|
516
|
+
const unfinished = refusedAttach(finished, request.locator);
|
|
517
|
+
if (unfinished !== null)
|
|
518
|
+
return unfinished;
|
|
519
|
+
const { path, revision } = (finished.body ?? {});
|
|
520
|
+
const root = `/rooms/${entry.handle}/`;
|
|
521
|
+
return typeof path === 'string' && path.startsWith(root) && typeof revision === 'string'
|
|
522
|
+
? { kind: 'attached', path: path.slice(root.length), revision }
|
|
523
|
+
: UNCERTAIN_READ;
|
|
487
524
|
},
|
|
488
525
|
reached(locator, version, target) {
|
|
489
526
|
return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
|
|
@@ -497,7 +534,10 @@ export function createAgentsHouseAdapter(options) {
|
|
|
497
534
|
roomStream: {
|
|
498
535
|
hold: held,
|
|
499
536
|
reopen: notifications.reopen,
|
|
500
|
-
observe
|
|
537
|
+
observe(notify, receipts, revisions) {
|
|
538
|
+
heldRevisions = revisions;
|
|
539
|
+
return notifications.observe(notify, receipts);
|
|
540
|
+
},
|
|
501
541
|
published(locator, roomVersion) {
|
|
502
542
|
const at = logPosition(roomVersion);
|
|
503
543
|
if (at !== null)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-reference.d.ts","sourceRoot":"","sources":["../src/app-reference.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,qBAAqB,QAEjC,CAAC"}
|