@agentshouse/mdruntime-adapter 0.1.0-cfdb3a66f1a92 → 0.1.0-cfe552b74bdf7
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 +3 -42
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +3 -332
- package/dist/adapter.d.ts +1 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +278 -277
- 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 +14 -12
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +65 -50
- package/dist/history.d.ts +12 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +91 -0
- package/dist/house-model.d.ts +892 -0
- package/dist/house-model.d.ts.map +1 -0
- package/dist/house-model.js +482 -0
- package/dist/index.d.ts +10 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/model.d.ts +5 -3
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +10 -32
- 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 +52 -43
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js +247 -203
- package/dist/refusal.d.ts +0 -3
- package/dist/refusal.d.ts.map +1 -1
- package/dist/refusal.js +2 -6
- 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 +125 -0
- package/dist/search.d.ts +5 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +47 -0
- package/dist/site.d.ts +1 -1
- package/dist/site.d.ts.map +1 -1
- package/dist/site.js +115 -130
- package/dist/stream.d.ts +12 -12
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +206 -158
- package/dist/transport.d.ts +81 -24
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +55 -11
- package/dist/upload.d.ts +12 -0
- package/dist/upload.d.ts.map +1 -0
- package/dist/upload.js +63 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/write.d.ts +18 -2
- package/dist/write.d.ts.map +1 -1
- package/dist/write.js +110 -71
- package/package.json +8 -5
- package/AGENTS.md +0 -318
- package/dist/gather.d.ts +0 -4
- package/dist/gather.d.ts.map +0 -1
- package/dist/gather.js +0 -45
- 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,102 +1,157 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { gathered, gatheredEach, selections } from './gather.js';
|
|
1
|
+
import { stepped } from '@agentshouse/mdruntime';
|
|
2
|
+
import { capabilities, discoveredRoom, mapDiscovery, parseAuthorityChange, parseDiscovery, trackedFrom, } from './discovery.js';
|
|
4
3
|
import { createSiteAdapter } from './site.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { authorityRoot, bootstrapping, parseFileAddress, parseGrant, } from './read.js';
|
|
5
|
+
import { roomStreams } from './room-stream.js';
|
|
6
|
+
import { sha256Hex } from './digest.js';
|
|
7
|
+
import { finishedUpload, houseUpload } from './upload.js';
|
|
7
8
|
import { houseAction } from './actions.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
9
|
+
import { houseSearch } from './search.js';
|
|
10
|
+
import { classifyDoor, doorRefusal } from './refusal.js';
|
|
11
|
+
import { houseNotifications, } from './stream.js';
|
|
12
|
+
import { houseDoorRoute, } from './transport.js';
|
|
13
|
+
import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, houseMove, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
|
|
14
|
+
import { HOUSE_NAMEABLE_ROOM_TYPES, HOUSE_ROOM_TYPES } from './room-types.js';
|
|
15
|
+
import { followsVersion, logPosition, presented, reachedVersion, roomVersion } from './version.js';
|
|
14
16
|
const UNCERTAIN = { kind: 'transport-uncertainty' };
|
|
15
17
|
const UNCERTAIN_READ = { kind: 'transport-uncertainty' };
|
|
18
|
+
const OCTET_STREAM = 'application/octet-stream';
|
|
19
|
+
const EXPIRED_ADDRESS = 403;
|
|
16
20
|
function lapsedGrant(outcome) {
|
|
17
21
|
return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
|
|
18
22
|
}
|
|
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 };
|
|
28
|
+
}
|
|
29
|
+
async function reused(scope, held) {
|
|
30
|
+
const sent = new Set(scope.contents.map((entry) => entry.path));
|
|
31
|
+
const contents = [...scope.contents];
|
|
32
|
+
const asked = scope.files.filter((file) => file.binary === undefined && file.outline === 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) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
contents.push({ path: file.path, content: found.content });
|
|
46
|
+
}
|
|
47
|
+
return { ...scope, contents };
|
|
48
|
+
}
|
|
19
49
|
export function createAgentsHouseAdapter(options) {
|
|
20
50
|
if ('site' in options)
|
|
21
|
-
return createSiteAdapter(options.site);
|
|
51
|
+
return createSiteAdapter(options.site, options.snapshot === true);
|
|
22
52
|
const transport = options.transport;
|
|
23
|
-
const notifications = houseNotifications(transport, 'credential'
|
|
53
|
+
const notifications = houseNotifications(transport, 'credential', {
|
|
54
|
+
opened: async (frame) => (frame === null ? null : opened(frame)),
|
|
55
|
+
changed,
|
|
56
|
+
});
|
|
24
57
|
const tracked = new Map();
|
|
25
|
-
const
|
|
58
|
+
const rooms = roomStreams('credential');
|
|
59
|
+
const unreused = new Set();
|
|
26
60
|
let bounds;
|
|
27
61
|
function declared() {
|
|
28
62
|
if (bounds === undefined)
|
|
29
63
|
throw new Error('no Discovery declared the bounds');
|
|
30
64
|
return bounds;
|
|
31
65
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
notifications.
|
|
41
|
-
const seen = [
|
|
42
|
-
...(continued?.seen ?? []),
|
|
43
|
-
...entry.schemas.filter((schema) => scope.files.some((file) => file.path === schema)),
|
|
44
|
-
];
|
|
45
|
-
const models = await partModels(scope, entry, new Set(seen), next === null);
|
|
46
|
-
const continuation = next === null ? null : { after: next, seen };
|
|
66
|
+
function opened(frame) {
|
|
67
|
+
const document = parseDiscovery(frame);
|
|
68
|
+
if (document === null)
|
|
69
|
+
return null;
|
|
70
|
+
bounds = document.bounds;
|
|
71
|
+
tracked.clear();
|
|
72
|
+
for (const entry of trackedFrom(document))
|
|
73
|
+
tracked.set(entry.locator, entry);
|
|
74
|
+
notifications.track([...tracked.values()]);
|
|
47
75
|
return {
|
|
48
|
-
...
|
|
49
|
-
|
|
50
|
-
...(continuation === null ? {} : { pageToken: JSON.stringify(continuation) }),
|
|
76
|
+
...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions)),
|
|
77
|
+
member: document.member,
|
|
51
78
|
};
|
|
52
79
|
}
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
if (
|
|
56
|
-
return { kind: 'room-not-readable', locator: ask.entry.locator };
|
|
57
|
-
notifications.reach(ask.entry.authority, view);
|
|
58
|
-
const answer = refreshFrom(view, ask.fromRoomVersion);
|
|
59
|
-
if (answer.kind !== 'net-advancement')
|
|
60
|
-
return answer;
|
|
61
|
-
handed.set(ask.entry.locator, enumeratedHanded(view, handed.get(ask.entry.locator)));
|
|
62
|
-
const models = await viewModels(view, ask.entry);
|
|
63
|
-
return models === undefined ? answer : { ...answer, models };
|
|
64
|
-
}
|
|
65
|
-
async function streamed(entry, request, from) {
|
|
66
|
-
const held = handed.get(entry.locator);
|
|
67
|
-
if (held?.roomVersion !== request.fromRoomVersion)
|
|
68
|
-
return null;
|
|
69
|
-
const advanced = notifications.advance(entry, from, (path, revision) => held.revisions.get(path) === revision);
|
|
70
|
-
if (advanced === null)
|
|
80
|
+
function changed(frame) {
|
|
81
|
+
const change = parseAuthorityChange(frame);
|
|
82
|
+
if (change === null)
|
|
71
83
|
return null;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
84
|
+
for (const locator of change.left)
|
|
85
|
+
tracked.delete(locator);
|
|
86
|
+
for (const entry of trackedFrom(change))
|
|
87
|
+
tracked.set(entry.locator, entry);
|
|
88
|
+
notifications.track([...tracked.values()]);
|
|
89
|
+
return {
|
|
90
|
+
kind: 'authority-changed',
|
|
91
|
+
rooms: change.authorities.map((authority) => discoveredRoom(authority)),
|
|
92
|
+
left: change.left,
|
|
93
|
+
member: change.member,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async function streamedPage(entry, token, held) {
|
|
97
|
+
const part = await rooms.part(entry, token);
|
|
98
|
+
if ('kind' in part)
|
|
99
|
+
return part;
|
|
100
|
+
const scope = await reused(part.scope, held);
|
|
101
|
+
if (scope === null) {
|
|
102
|
+
unreused.add(entry.locator);
|
|
103
|
+
return UNCERTAIN_READ;
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
...(await stepped(bootstrapping(scope))),
|
|
107
|
+
...(part.models === undefined ? {} : { models: part.models }),
|
|
108
|
+
...(part.pageToken === undefined ? {} : { pageToken: part.pageToken }),
|
|
109
|
+
bytes: part.bytes,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
async function namedDocument(entry, path, signal) {
|
|
113
|
+
const answered = await transport.streamed(houseDoorRoute('bootstrap'), { room_ref: entry.authority, prefixes: [`${authorityRoot(entry.handle)}/${path}`] }, signal ?? new AbortController().signal);
|
|
114
|
+
if (answered.room === null)
|
|
115
|
+
return classifyDoor(answered.status, answered.body, entry.locator) ?? UNCERTAIN_READ;
|
|
116
|
+
const opened = await rooms.head(entry, answered.room, undefined, signal);
|
|
117
|
+
if (opened.kind !== 'bootstrap')
|
|
118
|
+
return opened;
|
|
119
|
+
const files = [];
|
|
120
|
+
const contents = [];
|
|
121
|
+
let last = null;
|
|
122
|
+
for (let token = opened.pageToken; token !== undefined;) {
|
|
123
|
+
const part = await rooms.part(entry, token);
|
|
124
|
+
if ('kind' in part)
|
|
125
|
+
return part;
|
|
126
|
+
files.push(...part.scope.files);
|
|
127
|
+
contents.push(...part.scope.contents);
|
|
128
|
+
last = part.scope;
|
|
129
|
+
token = part.pageToken;
|
|
130
|
+
}
|
|
131
|
+
if (last === null)
|
|
132
|
+
return UNCERTAIN_READ;
|
|
133
|
+
return stepped(bootstrapping({ ...last, files, contents }));
|
|
134
|
+
}
|
|
135
|
+
async function openedRoom(entry, request) {
|
|
136
|
+
const reusing = unreused.delete(entry.locator) ? undefined : request.held;
|
|
137
|
+
const files = reusing === undefined
|
|
138
|
+
? null
|
|
139
|
+
: await heldFiles(entry.authority, reusing, declared().max_held_files);
|
|
140
|
+
const answered = await transport.streamed(houseDoorRoute('bootstrap'), {
|
|
141
|
+
room_ref: entry.authority,
|
|
142
|
+
prefixes: [authorityRoot(entry.handle)],
|
|
143
|
+
...(files === null ? {} : { held: [files] }),
|
|
144
|
+
}, request.signal ?? new AbortController().signal);
|
|
145
|
+
if (answered.room === null)
|
|
146
|
+
return classifyDoor(answered.status, answered.body, entry.locator) ?? UNCERTAIN_READ;
|
|
147
|
+
return rooms.head(entry, answered.room, request.arriving, request.signal);
|
|
75
148
|
}
|
|
76
149
|
function held(rooms) {
|
|
77
|
-
|
|
150
|
+
notifications.send(rooms.flatMap((room) => {
|
|
78
151
|
const entry = tracked.get(room.locator);
|
|
79
152
|
const at = logPosition(room.roomVersion);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
handed.set(room.locator, heldHanded(room));
|
|
83
|
-
notifications.reach(entry.authority, at);
|
|
84
|
-
return [presented(entry.authority, at)];
|
|
85
|
-
});
|
|
86
|
-
notifications.send(kept);
|
|
87
|
-
}
|
|
88
|
-
function adopted(entry, outcome) {
|
|
89
|
-
if (outcome.kind !== 'accepted')
|
|
90
|
-
return outcome;
|
|
91
|
-
const held = handed.get(entry.locator);
|
|
92
|
-
if (held === undefined)
|
|
93
|
-
return outcome;
|
|
94
|
-
const follows = followsVersion(held.roomVersion, outcome.roomVersion);
|
|
95
|
-
handed.set(entry.locator, acceptedHanded(held, outcome, follows ? outcome.roomVersion : held.roomVersion));
|
|
96
|
-
const at = logPosition(outcome.roomVersion);
|
|
97
|
-
if (follows && at !== null)
|
|
98
|
-
notifications.reach(entry.authority, at);
|
|
99
|
-
return outcome;
|
|
153
|
+
return entry === undefined || at === null ? [] : [presented(entry.authority, at)];
|
|
154
|
+
}));
|
|
100
155
|
}
|
|
101
156
|
function authorityFor(locator) {
|
|
102
157
|
return tracked.get(locator);
|
|
@@ -115,114 +170,25 @@ export function createAgentsHouseAdapter(options) {
|
|
|
115
170
|
return result;
|
|
116
171
|
return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
|
|
117
172
|
}
|
|
118
|
-
function weight(entry) {
|
|
119
|
-
return contentPrefixes(entry).length;
|
|
120
|
-
}
|
|
121
|
-
async function bootstrapped(entries, answer) {
|
|
122
|
-
let open = entries.map((entry, ask) => ({ entry, ask }));
|
|
123
|
-
while (open.length > 0) {
|
|
124
|
-
const unreached = [];
|
|
125
|
-
for (const part of selections(open, ({ entry }) => weight(entry), declared().max_prefixes)) {
|
|
126
|
-
const answered = await transport.door(DOOR_BOOTSTRAP, {
|
|
127
|
-
prefixes: part.flatMap(({ entry }) => contentPrefixes(entry)),
|
|
128
|
-
});
|
|
129
|
-
const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
|
|
130
|
-
const envelope = classified[0] === null ? parseEnvelope(answered.body) : null;
|
|
131
|
-
for (const [index, asked] of part.entries()) {
|
|
132
|
-
const failure = classified[index];
|
|
133
|
-
if (failure !== null && failure !== undefined) {
|
|
134
|
-
answer(asked.ask, failure);
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
if (envelope === null) {
|
|
138
|
-
answer(asked.ask, UNCERTAIN_READ);
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
const scope = envelope.scopes.find((candidate) => matchesScope(candidate, asked.entry));
|
|
142
|
-
if (scope === undefined && envelope.next !== undefined) {
|
|
143
|
-
unreached.push(asked);
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
answer(asked.ask, await bootstrappedRoom(envelope, scope, asked.entry, null));
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
open = unreached;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
async function continuedBootstrap(entry, continued) {
|
|
153
|
-
const after = { room: entry.authority, path: continued.after };
|
|
154
|
-
const answered = await transport.door(DOOR_BOOTSTRAP, {
|
|
155
|
-
prefixes: contentPrefixes(entry),
|
|
156
|
-
after,
|
|
157
|
-
});
|
|
158
|
-
const failure = classifyDoor(answered.status, answered.body, entry.locator);
|
|
159
|
-
if (failure !== null)
|
|
160
|
-
return failure;
|
|
161
|
-
const envelope = parseEnvelope(answered.body);
|
|
162
|
-
if (envelope === null)
|
|
163
|
-
return UNCERTAIN_READ;
|
|
164
|
-
const scope = envelope.scopes.find((candidate) => matchesScope(candidate, entry));
|
|
165
|
-
return bootstrappedRoom(envelope, scope, entry, continued);
|
|
166
|
-
}
|
|
167
|
-
function layered(asks) {
|
|
168
|
-
const depths = new Map();
|
|
169
|
-
const layers = [];
|
|
170
|
-
for (const ask of asks) {
|
|
171
|
-
const depth = depths.get(ask.entry.authority) ?? 0;
|
|
172
|
-
depths.set(ask.entry.authority, depth + 1);
|
|
173
|
-
(layers[depth] ??= []).push(ask);
|
|
174
|
-
}
|
|
175
|
-
return layers;
|
|
176
|
-
}
|
|
177
|
-
async function enumerated(asks) {
|
|
178
|
-
const answers = new Map();
|
|
179
|
-
const parts = layered(asks).flatMap((layer) => selections(layer, (ask) => weight(ask.entry), declared().max_prefixes));
|
|
180
|
-
for (const part of parts) {
|
|
181
|
-
let open = [...part];
|
|
182
|
-
while (open.length > 0) {
|
|
183
|
-
const answer = await transport.door(DOOR_ENUMERATE, {
|
|
184
|
-
prefixes: open.flatMap((ask) => contentPrefixes(ask.entry)),
|
|
185
|
-
positions: open.map((ask) => presented(ask.entry.authority, ask.from)),
|
|
186
|
-
});
|
|
187
|
-
const classified = open.map((ask) => classifyRead(answer.status, answer.body, ask.entry.locator));
|
|
188
|
-
if (classified[0]?.kind === 'beyond-horizon') {
|
|
189
|
-
const named = doorRefusal(answer.body)?.fields.authority;
|
|
190
|
-
const expired = open.filter((ask) => ask.entry.authority === named);
|
|
191
|
-
const answered = expired.length === 0 ? UNCERTAIN_READ : { kind: 'beyond-horizon' };
|
|
192
|
-
const gone = expired.length === 0 ? open : expired;
|
|
193
|
-
for (const ask of gone)
|
|
194
|
-
answers.set(ask, answered);
|
|
195
|
-
open = open.filter((ask) => !gone.includes(ask));
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
const enumeration = classified[0] === null ? parseEnumeration(answer.body, 'credential') : null;
|
|
199
|
-
for (const [index, ask] of open.entries()) {
|
|
200
|
-
const failure = classified[index];
|
|
201
|
-
if (failure !== null && failure !== undefined) {
|
|
202
|
-
answers.set(ask, failure);
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
answers.set(ask, enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask));
|
|
206
|
-
}
|
|
207
|
-
open = [];
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return asks.map((ask) => answers.get(ask) ?? UNCERTAIN_READ);
|
|
211
|
-
}
|
|
212
|
-
const bootstrapTogether = gatheredEach(bootstrapped);
|
|
213
|
-
const enumerateTogether = gathered(enumerated);
|
|
214
173
|
async function uploaded(head, changes, delta, bytes, entry, handed, keep) {
|
|
215
174
|
const paths = batchPaths(changes);
|
|
216
175
|
const sha256 = await deltaDigest(delta);
|
|
217
176
|
if (handed !== undefined && handed.sha256 === sha256) {
|
|
218
177
|
return { body: { ...head, paths, upload: handed, runtime: notifications.runtime } };
|
|
219
178
|
}
|
|
220
|
-
const minted = await transport.door(
|
|
179
|
+
const minted = await transport.door(houseDoorRoute('batch-upload'), {
|
|
180
|
+
...head,
|
|
181
|
+
paths,
|
|
182
|
+
bytes,
|
|
183
|
+
sha256,
|
|
184
|
+
});
|
|
221
185
|
if (minted.status !== 200) {
|
|
222
|
-
return {
|
|
186
|
+
return {
|
|
187
|
+
outcome: await stepped(mutationOutcome(minted.status, minted.body, entry.locator, entry.handle)),
|
|
188
|
+
};
|
|
223
189
|
}
|
|
224
190
|
const grant = parseGrant(minted.body);
|
|
225
|
-
if (grant === null
|
|
191
|
+
if (grant === null)
|
|
226
192
|
return { outcome: UNCERTAIN };
|
|
227
193
|
const delivered = await transport.transfer({
|
|
228
194
|
act: 'authored_batch_upload',
|
|
@@ -232,52 +198,52 @@ export function createAgentsHouseAdapter(options) {
|
|
|
232
198
|
if (delivered.status !== 200)
|
|
233
199
|
return { outcome: UNCERTAIN };
|
|
234
200
|
const holding = parseGrant(delivered.body);
|
|
235
|
-
if (holding === null
|
|
201
|
+
if (holding === null)
|
|
236
202
|
return { outcome: UNCERTAIN };
|
|
237
203
|
const upload = { url: holding.url, operation: holding.operation, bytes, sha256 };
|
|
238
204
|
await keep(upload);
|
|
239
205
|
return { body: { ...head, paths, upload, runtime: notifications.runtime } };
|
|
240
206
|
}
|
|
241
|
-
async function staged(answer, batch) {
|
|
207
|
+
async function staged(answer, { changeBatch: batch, file }) {
|
|
242
208
|
const refusal = doorRefusal(answer.body);
|
|
243
209
|
if (refusal?.code !== 'files_unstaged')
|
|
244
210
|
return null;
|
|
245
|
-
const
|
|
246
|
-
if (!Array.isArray(
|
|
211
|
+
const uploads = refusal.fields.upload;
|
|
212
|
+
if (!Array.isArray(uploads))
|
|
247
213
|
return false;
|
|
248
|
-
for (const
|
|
249
|
-
const
|
|
250
|
-
const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === sha256);
|
|
251
|
-
if (
|
|
214
|
+
for (const named of uploads) {
|
|
215
|
+
const upload = houseUpload(named);
|
|
216
|
+
const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === named.sha256);
|
|
217
|
+
if (upload === null || change === undefined)
|
|
252
218
|
return false;
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
url,
|
|
256
|
-
body: change.blob,
|
|
257
|
-
operation,
|
|
258
|
-
});
|
|
259
|
-
if (delivered.status !== 200)
|
|
219
|
+
const finished = await finishedUpload(transport, upload, await file(change.localId));
|
|
220
|
+
if (finished?.body?.staged !== true)
|
|
260
221
|
return false;
|
|
261
222
|
}
|
|
262
223
|
return true;
|
|
263
224
|
}
|
|
264
|
-
async function submit(
|
|
225
|
+
async function submit(request, head, changes, delta, bytes, entry, handed, keep, into) {
|
|
265
226
|
const inline = { ...head, changes, runtime: notifications.runtime };
|
|
266
227
|
const prepared = uploadable(inline, bytes, declared())
|
|
267
228
|
? await uploaded(head, changes, delta, bytes, entry, handed, keep)
|
|
268
229
|
: { body: inline };
|
|
269
230
|
if ('outcome' in prepared)
|
|
270
231
|
return prepared.outcome;
|
|
271
|
-
const answer = await transport.door(
|
|
272
|
-
const stagedFiles = await staged(answer,
|
|
232
|
+
const answer = await transport.door(houseDoorRoute('batch'), prepared.body);
|
|
233
|
+
const stagedFiles = await staged(answer, request);
|
|
273
234
|
if (stagedFiles === false)
|
|
274
235
|
return UNCERTAIN;
|
|
275
|
-
const settled = stagedFiles === null ? answer : await transport.door(
|
|
276
|
-
if (stagedFiles === true && (await staged(settled,
|
|
236
|
+
const settled = stagedFiles === null ? answer : await transport.door(houseDoorRoute('batch'), prepared.body);
|
|
237
|
+
if (stagedFiles === true && (await staged(settled, request)) !== null)
|
|
277
238
|
return UNCERTAIN;
|
|
278
|
-
return mutationOutcome(settled.status, settled.body, entry.locator, entry.handle
|
|
239
|
+
return stepped(mutationOutcome(settled.status, settled.body, entry.locator, entry.handle, into === undefined
|
|
240
|
+
? undefined
|
|
241
|
+
: {
|
|
242
|
+
source: { authority: entry.authority, handle: entry.handle },
|
|
243
|
+
destination: { authority: into.authority, handle: into.handle },
|
|
244
|
+
}));
|
|
279
245
|
}
|
|
280
|
-
|
|
246
|
+
function addressed(answer, locator) {
|
|
281
247
|
const failure = classifyDoor(answer.status, answer.body, locator);
|
|
282
248
|
if (failure !== null) {
|
|
283
249
|
return failure.kind === 'scoped-refusal'
|
|
@@ -289,85 +255,104 @@ export function createAgentsHouseAdapter(options) {
|
|
|
289
255
|
}
|
|
290
256
|
: failure;
|
|
291
257
|
}
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
258
|
+
const address = parseFileAddress(answer.body);
|
|
259
|
+
return address === null ? UNCERTAIN_READ : { kind: 'address', ...address };
|
|
260
|
+
}
|
|
261
|
+
function linked(answer, locator) {
|
|
262
|
+
const address = addressed(answer, locator);
|
|
263
|
+
return address.kind === 'address'
|
|
264
|
+
? { kind: 'link', url: address.url, name: address.name, mediaType: address.mediaType }
|
|
265
|
+
: address;
|
|
266
|
+
}
|
|
267
|
+
function refusedAttach(answer, locator) {
|
|
268
|
+
const failure = classifyDoor(answer.status, answer.body, locator);
|
|
269
|
+
if (failure === null)
|
|
270
|
+
return null;
|
|
271
|
+
return failure.kind === 'scoped-refusal'
|
|
272
|
+
? {
|
|
273
|
+
kind: 'rejected',
|
|
274
|
+
code: failure.code,
|
|
275
|
+
fields: failure.fields,
|
|
276
|
+
retryable: failure.retryable,
|
|
277
|
+
}
|
|
278
|
+
: failure;
|
|
279
|
+
}
|
|
280
|
+
async function downloaded(ask, locator) {
|
|
281
|
+
let address = addressed(await ask(), locator);
|
|
282
|
+
if (address.kind !== 'address')
|
|
283
|
+
return address;
|
|
284
|
+
let read = await transport.download(address.url);
|
|
285
|
+
if (read.status === EXPIRED_ADDRESS) {
|
|
286
|
+
address = addressed(await ask(), locator);
|
|
287
|
+
if (address.kind !== 'address')
|
|
288
|
+
return address;
|
|
289
|
+
read = await transport.download(address.url);
|
|
290
|
+
}
|
|
291
|
+
if (read.status !== 200 || read.body === null)
|
|
297
292
|
return UNCERTAIN_READ;
|
|
298
|
-
return {
|
|
293
|
+
return {
|
|
294
|
+
kind: 'binary',
|
|
295
|
+
body: read.body,
|
|
296
|
+
mediaType: address.mediaType,
|
|
297
|
+
sha256: address.sha256,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function pathAddress(request) {
|
|
301
|
+
const entry = authorityFor(request.locator);
|
|
302
|
+
if (entry === undefined)
|
|
303
|
+
return null;
|
|
304
|
+
return () => transport.door(houseDoorRoute('files/get'), {
|
|
305
|
+
room_ref: entry.authority,
|
|
306
|
+
path: request.path,
|
|
307
|
+
revision: request.revision,
|
|
308
|
+
});
|
|
299
309
|
}
|
|
300
310
|
return {
|
|
311
|
+
roomTypes: HOUSE_ROOM_TYPES,
|
|
312
|
+
nameableRoomTypes: HOUSE_NAMEABLE_ROOM_TYPES,
|
|
301
313
|
async authorize() {
|
|
302
314
|
return options.authorize();
|
|
303
315
|
},
|
|
304
|
-
async discover(_authorization) {
|
|
305
|
-
const answer = await transport.door(DOOR_DISCOVER, { prefixes: [...DISCOVER_PREFIXES] });
|
|
306
|
-
const failed = discoveryFailure(answer.status, answer.body);
|
|
307
|
-
if (failed !== null)
|
|
308
|
-
return failed;
|
|
309
|
-
const document = parseDiscovery(answer.body);
|
|
310
|
-
if (document === null)
|
|
311
|
-
return { kind: 'transport-uncertainty' };
|
|
312
|
-
bounds = document.bounds;
|
|
313
|
-
const entries = trackedFrom(document);
|
|
314
|
-
tracked.clear();
|
|
315
|
-
for (const entry of entries)
|
|
316
|
-
tracked.set(entry.locator, entry);
|
|
317
|
-
for (const locator of handed.keys())
|
|
318
|
-
if (!tracked.has(locator))
|
|
319
|
-
handed.delete(locator);
|
|
320
|
-
notifications.track(entries);
|
|
321
|
-
return {
|
|
322
|
-
...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document)),
|
|
323
|
-
member: document.member,
|
|
324
|
-
};
|
|
325
|
-
},
|
|
326
316
|
async bootstrap(_authorization, request) {
|
|
327
317
|
const entry = authorityFor(request.locator);
|
|
328
318
|
if (entry === undefined)
|
|
329
319
|
return { kind: 'room-not-readable', locator: request.locator };
|
|
330
|
-
if (request.
|
|
331
|
-
return
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
async refresh(_authorization, request) {
|
|
336
|
-
const entry = authorityFor(request.locator);
|
|
337
|
-
if (entry === undefined)
|
|
338
|
-
return { kind: 'room-not-readable', locator: request.locator };
|
|
339
|
-
if (request.pageToken !== undefined)
|
|
340
|
-
return { kind: 'transport-uncertainty' };
|
|
341
|
-
const from = logPosition(request.fromRoomVersion);
|
|
342
|
-
if (from === null)
|
|
343
|
-
return { kind: 'beyond-horizon' };
|
|
344
|
-
const answered = await streamed(entry, request, from);
|
|
345
|
-
if (answered !== null)
|
|
346
|
-
return answered;
|
|
347
|
-
return enumerateTogether({ entry, from, fromRoomVersion: request.fromRoomVersion });
|
|
320
|
+
if (request.path !== undefined)
|
|
321
|
+
return namedDocument(entry, request.path, request.signal);
|
|
322
|
+
return request.pageToken === undefined
|
|
323
|
+
? openedRoom(entry, request)
|
|
324
|
+
: streamedPage(entry, request.pageToken, request.held);
|
|
348
325
|
},
|
|
349
326
|
async mutate(_authorization, request, keep) {
|
|
350
327
|
const entry = authorityFor(request.locator);
|
|
351
328
|
if (entry === undefined)
|
|
352
329
|
return { kind: 'room-not-readable', locator: request.locator };
|
|
353
|
-
const
|
|
330
|
+
const batch = request.changeBatch;
|
|
331
|
+
const destination = batch.between?.destination.room;
|
|
332
|
+
const into = destination === undefined ? undefined : authorityFor(destination);
|
|
333
|
+
if (destination !== undefined && into === undefined) {
|
|
334
|
+
return { kind: 'room-not-readable', locator: destination };
|
|
335
|
+
}
|
|
336
|
+
const mapped = into === undefined
|
|
337
|
+
? houseChanges(batch, entry.handle)
|
|
338
|
+
: houseMove(batch, entry.handle, into.handle);
|
|
354
339
|
if (mapped.kind === 'unmapped')
|
|
355
340
|
return rejectedMapping(mapped);
|
|
356
|
-
const head = operationHead(
|
|
341
|
+
const head = operationHead(batch, entry.authority);
|
|
357
342
|
const delta = deltaBody(mapped.changes);
|
|
358
343
|
const bytes = deltaBytes(delta);
|
|
359
344
|
const handed = request.upload;
|
|
360
|
-
const
|
|
361
|
-
const outcome = await
|
|
345
|
+
const answer = (upload) => submit(request, head, mapped.changes, delta, bytes, entry, upload, keep, into);
|
|
346
|
+
const outcome = await answer(handed);
|
|
362
347
|
if (handed === undefined || !lapsedGrant(outcome))
|
|
363
|
-
return
|
|
364
|
-
return
|
|
348
|
+
return outcome;
|
|
349
|
+
return answer(undefined);
|
|
365
350
|
},
|
|
366
351
|
async history(_authorization, request) {
|
|
367
352
|
const entry = authorityFor(request.locator);
|
|
368
353
|
if (entry === undefined)
|
|
369
354
|
return { kind: 'room-not-readable', locator: request.locator };
|
|
370
|
-
const answer = await transport.door(
|
|
355
|
+
const answer = await transport.door(houseDoorRoute('revisions'), revisionBody({
|
|
371
356
|
path: request.path,
|
|
372
357
|
handle: entry.handle,
|
|
373
358
|
...(request.revision === undefined ? {} : { revision: request.revision }),
|
|
@@ -379,35 +364,45 @@ export function createAgentsHouseAdapter(options) {
|
|
|
379
364
|
});
|
|
380
365
|
},
|
|
381
366
|
async binary(_authorization, request) {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const answer = await transport.door(DOOR_FILES_GET, {
|
|
387
|
-
room_ref: entry.authority,
|
|
388
|
-
path: request.path,
|
|
389
|
-
revision: request.revision,
|
|
390
|
-
});
|
|
391
|
-
return downloaded(answer, request.locator);
|
|
392
|
-
}
|
|
393
|
-
const referrer = houseReferrer(request.referrer, tracked);
|
|
394
|
-
if (referrer === null) {
|
|
395
|
-
return { kind: 'rejected', code: 'referrer_unknown', fields: {}, retryable: false };
|
|
396
|
-
}
|
|
397
|
-
const answer = await transport.door(DOOR_FILES_GET, {
|
|
398
|
-
file: request.fileRef,
|
|
399
|
-
referrer: referrer.referrer,
|
|
400
|
-
});
|
|
401
|
-
return downloaded(answer, referrer.locator);
|
|
367
|
+
const ask = pathAddress(request);
|
|
368
|
+
if (ask === null)
|
|
369
|
+
return { kind: 'room-not-readable', locator: request.locator };
|
|
370
|
+
return downloaded(ask, request.locator);
|
|
402
371
|
},
|
|
403
|
-
|
|
404
|
-
const
|
|
405
|
-
if (
|
|
406
|
-
return
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
372
|
+
async link(_authorization, request) {
|
|
373
|
+
const ask = pathAddress(request);
|
|
374
|
+
if (ask === null)
|
|
375
|
+
return { kind: 'room-not-readable', locator: request.locator };
|
|
376
|
+
return linked(await ask(), request.locator);
|
|
377
|
+
},
|
|
378
|
+
async attach(_authorization, request) {
|
|
379
|
+
const entry = authorityFor(request.locator);
|
|
380
|
+
if (entry === undefined)
|
|
381
|
+
return { kind: 'room-not-readable', locator: request.locator };
|
|
382
|
+
const declared = await transport.door(houseDoorRoute('files/attach'), {
|
|
383
|
+
room_ref: entry.authority,
|
|
384
|
+
name: request.name,
|
|
385
|
+
bytes: request.byteLength,
|
|
386
|
+
sha256: request.sha256,
|
|
387
|
+
media_type: request.mediaType.length > 0 ? request.mediaType : OCTET_STREAM,
|
|
388
|
+
});
|
|
389
|
+
const refused = refusedAttach(declared, request.locator);
|
|
390
|
+
if (refused !== null)
|
|
391
|
+
return refused;
|
|
392
|
+
const upload = houseUpload(declared.body?.upload);
|
|
393
|
+
if (upload === null)
|
|
394
|
+
return UNCERTAIN_READ;
|
|
395
|
+
const finished = await finishedUpload(transport, upload, request.blob);
|
|
396
|
+
if (finished === null)
|
|
397
|
+
return UNCERTAIN_READ;
|
|
398
|
+
const unfinished = refusedAttach(finished, request.locator);
|
|
399
|
+
if (unfinished !== null)
|
|
400
|
+
return unfinished;
|
|
401
|
+
const { path, revision } = (finished.body ?? {});
|
|
402
|
+
const root = `/rooms/${entry.handle}/`;
|
|
403
|
+
return typeof path === 'string' && path.startsWith(root) && typeof revision === 'string'
|
|
404
|
+
? { kind: 'attached', path: path.slice(root.length), revision }
|
|
405
|
+
: UNCERTAIN_READ;
|
|
411
406
|
},
|
|
412
407
|
reached(locator, version, target) {
|
|
413
408
|
return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
|
|
@@ -418,7 +413,13 @@ export function createAgentsHouseAdapter(options) {
|
|
|
418
413
|
async action(_authorization, request) {
|
|
419
414
|
return withAt(await houseAction(transport, request));
|
|
420
415
|
},
|
|
421
|
-
|
|
416
|
+
search(_authorization, request) {
|
|
417
|
+
return houseSearch(transport, [...tracked.values()], request);
|
|
418
|
+
},
|
|
419
|
+
roomStream: {
|
|
420
|
+
hold: held,
|
|
421
|
+
observe: notifications.observe,
|
|
422
|
+
},
|
|
422
423
|
conversations: notifications.conversations,
|
|
423
424
|
serviceMode: notifications.serviceMode,
|
|
424
425
|
};
|