@agentshouse/mdruntime-adapter 0.1.0-ca3a44982cbe7 → 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 +15 -7
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +33 -146
- package/dist/adapter.d.ts +1 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +296 -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 +7 -10
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +39 -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 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +10 -15
- 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 +66 -18
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js +318 -128
- package/dist/refusal.d.ts.map +1 -1
- package/dist/refusal.js +2 -1
- 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 +1 -1
- package/dist/site.d.ts.map +1 -1
- package/dist/site.js +115 -84
- package/dist/stream.d.ts +5 -4
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +355 -45
- package/dist/transport.d.ts +66 -11
- 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/version.d.ts +1 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/write.d.ts +16 -1
- package/dist/write.d.ts.map +1 -1
- package/dist/write.js +106 -57
- package/package.json +8 -5
- package/AGENTS.md +0 -299
- package/dist/handed.d.ts +0 -15
- package/dist/handed.d.ts.map +0 -1
- package/dist/handed.js +0 -38
- package/dist/referrer.d.ts +0 -7
- package/dist/referrer.d.ts.map +0 -1
- package/dist/referrer.js +0 -35
package/dist/adapter.js
CHANGED
|
@@ -1,75 +1,146 @@
|
|
|
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 {
|
|
6
|
+
import { authorityRoot, bootstrapping, parseEnumeration, parseFileAddress, parseGrant, refreshFrom, viewOf, wholeView, } from './read.js';
|
|
7
|
+
import { roomStreams } from './room-stream.js';
|
|
8
|
+
import { sha256Hex } from './digest.js';
|
|
9
|
+
import { finishedUpload, houseUpload } from './upload.js';
|
|
7
10
|
import { houseAction } from './actions.js';
|
|
8
11
|
import { classifyDoor, classifyRead, discoveryFailure, doorRefusal } from './refusal.js';
|
|
9
12
|
import { houseNotifications } from './stream.js';
|
|
10
|
-
import { DOOR_BATCH, DOOR_BATCH_UPLOAD, DOOR_BOOTSTRAP, DOOR_DISCOVER, DOOR_ENUMERATE, DOOR_FILES_GET, DOOR_REVISIONS, } from './transport.js';
|
|
11
|
-
import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
|
|
12
|
-
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';
|
|
13
16
|
import { followsVersion, logPosition, presented, reachedVersion, roomVersion, } from './version.js';
|
|
14
17
|
const UNCERTAIN = { kind: 'transport-uncertainty' };
|
|
15
18
|
const UNCERTAIN_READ = { kind: 'transport-uncertainty' };
|
|
19
|
+
const OCTET_STREAM = 'application/octet-stream';
|
|
20
|
+
const EXPIRED_ADDRESS = 403;
|
|
16
21
|
function lapsedGrant(outcome) {
|
|
17
22
|
return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
|
|
18
23
|
}
|
|
24
|
+
async function heldFiles(authority, held, bound) {
|
|
25
|
+
const listed = await held.files(null, bound + 1);
|
|
26
|
+
const files = listed.slice(0, bound);
|
|
27
|
+
const through = listed.length > bound ? (files.at(-1)?.path ?? null) : null;
|
|
28
|
+
return files.length === 0 ? null : { room: authority, through, files };
|
|
29
|
+
}
|
|
30
|
+
async function reused(scope, held) {
|
|
31
|
+
const sent = new Set(scope.contents.map((entry) => entry.path));
|
|
32
|
+
const contents = [...scope.contents];
|
|
33
|
+
const asked = scope.files.filter((file) => file.binary === undefined && !sent.has(file.path));
|
|
34
|
+
if (asked.length === 0)
|
|
35
|
+
return scope;
|
|
36
|
+
if (held === undefined)
|
|
37
|
+
return null;
|
|
38
|
+
const kept = await held.contents(asked.map(({ path }) => path));
|
|
39
|
+
for (const file of asked) {
|
|
40
|
+
const found = kept.get(file.path);
|
|
41
|
+
if (found === undefined ||
|
|
42
|
+
found.revision !== file.revision ||
|
|
43
|
+
(await sha256Hex(found.content)) !== file.sha256) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
contents.push({ path: file.path, content: found.content });
|
|
47
|
+
}
|
|
48
|
+
return { ...scope, contents };
|
|
49
|
+
}
|
|
19
50
|
export function createAgentsHouseAdapter(options) {
|
|
20
51
|
if ('site' in options)
|
|
21
|
-
return createSiteAdapter(options.site);
|
|
52
|
+
return createSiteAdapter(options.site, options.snapshot === true);
|
|
22
53
|
const transport = options.transport;
|
|
23
54
|
const notifications = houseNotifications(transport, 'credential');
|
|
24
55
|
const tracked = new Map();
|
|
25
56
|
const handed = new Map();
|
|
57
|
+
const rooms = roomStreams('credential');
|
|
58
|
+
const unreused = new Set();
|
|
59
|
+
let heldRevisions;
|
|
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
|
-
async function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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);
|
|
71
|
+
if (scope === null) {
|
|
72
|
+
unreused.add(entry.locator);
|
|
73
|
+
return UNCERTAIN_READ;
|
|
74
|
+
}
|
|
75
|
+
if (part.first && part.ended)
|
|
76
|
+
handed.set(entry.locator, roomVersion(scope));
|
|
38
77
|
else
|
|
39
78
|
handed.delete(entry.locator);
|
|
40
79
|
notifications.reach(entry.authority, scope);
|
|
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 };
|
|
47
80
|
return {
|
|
48
|
-
...
|
|
49
|
-
...(models === undefined ? {} : { models }),
|
|
50
|
-
...(
|
|
81
|
+
...(await stepped(bootstrapping(scope))),
|
|
82
|
+
...(part.models === undefined ? {} : { models: part.models }),
|
|
83
|
+
...(part.pageToken === undefined ? {} : { pageToken: part.pageToken }),
|
|
84
|
+
bytes: part.bytes,
|
|
51
85
|
};
|
|
52
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
|
+
}
|
|
100
|
+
async function enumeratedPage(ask, page) {
|
|
101
|
+
const answer = await transport.door(DOOR_ENUMERATE, {
|
|
102
|
+
prefixes: [authorityRoot(ask.entry.handle)],
|
|
103
|
+
positions: [
|
|
104
|
+
{
|
|
105
|
+
...presented(ask.entry.authority, { position: page.after, logEpoch: ask.from.logEpoch }),
|
|
106
|
+
page: { through: page.through, read: page.read },
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
});
|
|
110
|
+
const failure = classifyRead(answer.status, answer.body, ask.entry.locator);
|
|
111
|
+
if (failure !== null)
|
|
112
|
+
return failure;
|
|
113
|
+
const enumeration = parseEnumeration(answer.body, 'credential');
|
|
114
|
+
if (enumeration === null)
|
|
115
|
+
return UNCERTAIN_READ;
|
|
116
|
+
return (viewOf(enumeration, ask.entry) ?? { kind: 'room-not-readable', locator: ask.entry.locator });
|
|
117
|
+
}
|
|
53
118
|
async function enumeratedRoom(enumeration, ask) {
|
|
54
|
-
const
|
|
55
|
-
if (
|
|
119
|
+
const first = viewOf(enumeration, ask.entry);
|
|
120
|
+
if (first === undefined)
|
|
56
121
|
return { kind: 'room-not-readable', locator: ask.entry.locator };
|
|
122
|
+
const view = await wholeView(first, (page) => enumeratedPage(ask, page));
|
|
123
|
+
if ('kind' in view)
|
|
124
|
+
return view;
|
|
57
125
|
notifications.reach(ask.entry.authority, view);
|
|
58
|
-
const answer = refreshFrom(view, ask.fromRoomVersion);
|
|
126
|
+
const answer = await refreshFrom(view, ask.fromRoomVersion);
|
|
59
127
|
if (answer.kind !== 'net-advancement')
|
|
60
128
|
return answer;
|
|
61
|
-
handed.set(ask.entry.locator,
|
|
129
|
+
handed.set(ask.entry.locator, roomVersion(view));
|
|
62
130
|
const models = await viewModels(view, ask.entry);
|
|
63
131
|
return models === undefined ? answer : { ...answer, models };
|
|
64
132
|
}
|
|
65
133
|
async function streamed(entry, request, from) {
|
|
66
|
-
|
|
67
|
-
if (held?.roomVersion !== request.fromRoomVersion)
|
|
134
|
+
if (handed.get(entry.locator) !== request.fromRoomVersion)
|
|
68
135
|
return null;
|
|
69
|
-
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));
|
|
70
141
|
if (advanced === null)
|
|
71
142
|
return null;
|
|
72
|
-
handed.set(entry.locator,
|
|
143
|
+
handed.set(entry.locator, advanced.page.roomVersion);
|
|
73
144
|
const models = await streamedModels(advanced.schemas, entry, 'credential');
|
|
74
145
|
return models === undefined ? advanced.page : { ...advanced.page, models };
|
|
75
146
|
}
|
|
@@ -79,20 +150,27 @@ export function createAgentsHouseAdapter(options) {
|
|
|
79
150
|
const at = logPosition(room.roomVersion);
|
|
80
151
|
if (entry === undefined || at === null)
|
|
81
152
|
return [];
|
|
82
|
-
handed.set(room.locator,
|
|
153
|
+
handed.set(room.locator, room.roomVersion);
|
|
83
154
|
notifications.reach(entry.authority, at);
|
|
84
155
|
return [presented(entry.authority, at)];
|
|
85
156
|
});
|
|
86
157
|
notifications.send(kept);
|
|
87
158
|
}
|
|
88
|
-
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) {
|
|
89
166
|
if (outcome.kind !== 'accepted')
|
|
90
167
|
return outcome;
|
|
91
168
|
const held = handed.get(entry.locator);
|
|
92
169
|
if (held === undefined)
|
|
93
170
|
return outcome;
|
|
94
|
-
const follows = followsVersion(held
|
|
95
|
-
|
|
171
|
+
const follows = followsVersion(held, outcome.roomVersion);
|
|
172
|
+
if (follows)
|
|
173
|
+
handed.set(entry.locator, outcome.roomVersion);
|
|
96
174
|
const at = logPosition(outcome.roomVersion);
|
|
97
175
|
if (follows && at !== null)
|
|
98
176
|
notifications.reach(entry.authority, at);
|
|
@@ -115,101 +193,52 @@ export function createAgentsHouseAdapter(options) {
|
|
|
115
193
|
return result;
|
|
116
194
|
return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
|
|
117
195
|
}
|
|
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
196
|
function layered(asks) {
|
|
168
197
|
const depths = new Map();
|
|
169
198
|
const layers = [];
|
|
170
199
|
for (const ask of asks) {
|
|
171
|
-
const depth = depths.get(ask.entry.authority) ?? 0;
|
|
172
|
-
depths.set(ask.entry.authority, depth + 1);
|
|
200
|
+
const depth = depths.get(ask.ask.entry.authority) ?? 0;
|
|
201
|
+
depths.set(ask.ask.entry.authority, depth + 1);
|
|
173
202
|
(layers[depth] ??= []).push(ask);
|
|
174
203
|
}
|
|
175
204
|
return layers;
|
|
176
205
|
}
|
|
177
206
|
async function enumerated(asks) {
|
|
178
|
-
const answers =
|
|
179
|
-
const parts = layered(asks
|
|
207
|
+
const answers = [];
|
|
208
|
+
const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, declared().max_prefixes));
|
|
180
209
|
for (const part of parts) {
|
|
181
210
|
let open = [...part];
|
|
182
211
|
while (open.length > 0) {
|
|
183
212
|
const answer = await transport.door(DOOR_ENUMERATE, {
|
|
184
|
-
prefixes: open.
|
|
185
|
-
positions: open.map((ask) => presented(ask.entry.authority, ask.from)),
|
|
213
|
+
prefixes: open.map(({ ask }) => authorityRoot(ask.entry.handle)),
|
|
214
|
+
positions: open.map(({ ask }) => presented(ask.entry.authority, ask.from)),
|
|
186
215
|
});
|
|
187
|
-
const classified = open.map((ask) => classifyRead(answer.status, answer.body, ask.entry.locator));
|
|
216
|
+
const classified = open.map(({ ask }) => classifyRead(answer.status, answer.body, ask.entry.locator));
|
|
188
217
|
if (classified[0]?.kind === 'beyond-horizon') {
|
|
189
218
|
const named = doorRefusal(answer.body)?.fields.authority;
|
|
190
|
-
const expired = open.filter((ask) => ask.entry.authority === named);
|
|
219
|
+
const expired = open.filter(({ ask }) => ask.entry.authority === named);
|
|
191
220
|
const answered = expired.length === 0 ? UNCERTAIN_READ : { kind: 'beyond-horizon' };
|
|
192
221
|
const gone = expired.length === 0 ? open : expired;
|
|
193
222
|
for (const ask of gone)
|
|
194
|
-
answers.
|
|
223
|
+
answers[ask.index] = answered;
|
|
195
224
|
open = open.filter((ask) => !gone.includes(ask));
|
|
196
225
|
continue;
|
|
197
226
|
}
|
|
198
227
|
const enumeration = classified[0] === null ? parseEnumeration(answer.body, 'credential') : null;
|
|
199
|
-
for (const [
|
|
200
|
-
const failure = classified[
|
|
228
|
+
for (const [answerIndex, { ask, index }] of open.entries()) {
|
|
229
|
+
const failure = classified[answerIndex];
|
|
201
230
|
if (failure !== null && failure !== undefined) {
|
|
202
|
-
answers
|
|
231
|
+
answers[index] = failure;
|
|
203
232
|
continue;
|
|
204
233
|
}
|
|
205
|
-
answers
|
|
234
|
+
answers[index] =
|
|
235
|
+
enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask);
|
|
206
236
|
}
|
|
207
237
|
open = [];
|
|
208
238
|
}
|
|
209
239
|
}
|
|
210
|
-
return
|
|
240
|
+
return answers;
|
|
211
241
|
}
|
|
212
|
-
const bootstrapTogether = gatheredEach(bootstrapped);
|
|
213
242
|
const enumerateTogether = gathered(enumerated);
|
|
214
243
|
async function uploaded(head, changes, delta, bytes, entry, handed, keep) {
|
|
215
244
|
const paths = batchPaths(changes);
|
|
@@ -219,10 +248,12 @@ export function createAgentsHouseAdapter(options) {
|
|
|
219
248
|
}
|
|
220
249
|
const minted = await transport.door(DOOR_BATCH_UPLOAD, { ...head, paths, bytes, sha256 });
|
|
221
250
|
if (minted.status !== 200) {
|
|
222
|
-
return {
|
|
251
|
+
return {
|
|
252
|
+
outcome: await stepped(mutationOutcome(minted.status, minted.body, entry.locator, entry.handle)),
|
|
253
|
+
};
|
|
223
254
|
}
|
|
224
255
|
const grant = parseGrant(minted.body);
|
|
225
|
-
if (grant === null
|
|
256
|
+
if (grant === null)
|
|
226
257
|
return { outcome: UNCERTAIN };
|
|
227
258
|
const delivered = await transport.transfer({
|
|
228
259
|
act: 'authored_batch_upload',
|
|
@@ -232,36 +263,31 @@ export function createAgentsHouseAdapter(options) {
|
|
|
232
263
|
if (delivered.status !== 200)
|
|
233
264
|
return { outcome: UNCERTAIN };
|
|
234
265
|
const holding = parseGrant(delivered.body);
|
|
235
|
-
if (holding === null
|
|
266
|
+
if (holding === null)
|
|
236
267
|
return { outcome: UNCERTAIN };
|
|
237
268
|
const upload = { url: holding.url, operation: holding.operation, bytes, sha256 };
|
|
238
269
|
await keep(upload);
|
|
239
270
|
return { body: { ...head, paths, upload, runtime: notifications.runtime } };
|
|
240
271
|
}
|
|
241
|
-
async function staged(answer, batch) {
|
|
272
|
+
async function staged(answer, { changeBatch: batch, file }) {
|
|
242
273
|
const refusal = doorRefusal(answer.body);
|
|
243
274
|
if (refusal?.code !== 'files_unstaged')
|
|
244
275
|
return null;
|
|
245
|
-
const
|
|
246
|
-
if (!Array.isArray(
|
|
276
|
+
const uploads = refusal.fields.upload;
|
|
277
|
+
if (!Array.isArray(uploads))
|
|
247
278
|
return false;
|
|
248
|
-
for (const
|
|
249
|
-
const
|
|
250
|
-
const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === sha256);
|
|
251
|
-
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)
|
|
252
283
|
return false;
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
url,
|
|
256
|
-
body: change.blob,
|
|
257
|
-
operation,
|
|
258
|
-
});
|
|
259
|
-
if (delivered.status !== 200)
|
|
284
|
+
const finished = await finishedUpload(transport, upload, await file(change.localId));
|
|
285
|
+
if (finished?.body?.staged !== true)
|
|
260
286
|
return false;
|
|
261
287
|
}
|
|
262
288
|
return true;
|
|
263
289
|
}
|
|
264
|
-
async function submit(
|
|
290
|
+
async function submit(request, head, changes, delta, bytes, entry, handed, keep, into) {
|
|
265
291
|
const inline = { ...head, changes, runtime: notifications.runtime };
|
|
266
292
|
const prepared = uploadable(inline, bytes, declared())
|
|
267
293
|
? await uploaded(head, changes, delta, bytes, entry, handed, keep)
|
|
@@ -269,15 +295,20 @@ export function createAgentsHouseAdapter(options) {
|
|
|
269
295
|
if ('outcome' in prepared)
|
|
270
296
|
return prepared.outcome;
|
|
271
297
|
const answer = await transport.door(DOOR_BATCH, prepared.body);
|
|
272
|
-
const stagedFiles = await staged(answer,
|
|
298
|
+
const stagedFiles = await staged(answer, request);
|
|
273
299
|
if (stagedFiles === false)
|
|
274
300
|
return UNCERTAIN;
|
|
275
301
|
const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
|
|
276
|
-
if (stagedFiles === true && (await staged(settled,
|
|
302
|
+
if (stagedFiles === true && (await staged(settled, request)) !== null)
|
|
277
303
|
return UNCERTAIN;
|
|
278
|
-
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
|
+
}));
|
|
279
310
|
}
|
|
280
|
-
|
|
311
|
+
function addressed(answer, locator) {
|
|
281
312
|
const failure = classifyDoor(answer.status, answer.body, locator);
|
|
282
313
|
if (failure !== null) {
|
|
283
314
|
return failure.kind === 'scoped-refusal'
|
|
@@ -289,20 +320,69 @@ export function createAgentsHouseAdapter(options) {
|
|
|
289
320
|
}
|
|
290
321
|
: failure;
|
|
291
322
|
}
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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)
|
|
297
357
|
return UNCERTAIN_READ;
|
|
298
|
-
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
|
+
});
|
|
299
374
|
}
|
|
300
375
|
return {
|
|
376
|
+
roomTypes: HOUSE_ROOM_TYPES,
|
|
377
|
+
nameableRoomTypes: HOUSE_NAMEABLE_ROOM_TYPES,
|
|
301
378
|
async authorize() {
|
|
302
379
|
return options.authorize();
|
|
303
380
|
},
|
|
304
|
-
async discover(_authorization) {
|
|
305
|
-
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
|
+
});
|
|
306
386
|
const failed = discoveryFailure(answer.status, answer.body);
|
|
307
387
|
if (failed !== null)
|
|
308
388
|
return failed;
|
|
@@ -311,26 +391,40 @@ export function createAgentsHouseAdapter(options) {
|
|
|
311
391
|
return { kind: 'transport-uncertainty' };
|
|
312
392
|
bounds = document.bounds;
|
|
313
393
|
const entries = trackedFrom(document);
|
|
314
|
-
|
|
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);
|
|
315
409
|
for (const entry of entries)
|
|
316
410
|
tracked.set(entry.locator, entry);
|
|
317
411
|
for (const locator of handed.keys())
|
|
318
412
|
if (!tracked.has(locator))
|
|
319
413
|
handed.delete(locator);
|
|
320
|
-
notifications.track(
|
|
414
|
+
notifications.track([...tracked.values()]);
|
|
321
415
|
return {
|
|
322
|
-
...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document)),
|
|
416
|
+
...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions, [...tracked.values()])),
|
|
323
417
|
member: document.member,
|
|
418
|
+
...(covered === null ? {} : { covered }),
|
|
324
419
|
};
|
|
325
420
|
},
|
|
326
421
|
async bootstrap(_authorization, request) {
|
|
327
422
|
const entry = authorityFor(request.locator);
|
|
328
423
|
if (entry === undefined)
|
|
329
424
|
return { kind: 'room-not-readable', locator: request.locator };
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
return bootstrapTogether(entry);
|
|
425
|
+
return request.pageToken === undefined
|
|
426
|
+
? openedRoom(entry, request)
|
|
427
|
+
: streamedPage(entry, request.pageToken, request.held);
|
|
334
428
|
},
|
|
335
429
|
async refresh(_authorization, request) {
|
|
336
430
|
const entry = authorityFor(request.locator);
|
|
@@ -350,18 +444,27 @@ export function createAgentsHouseAdapter(options) {
|
|
|
350
444
|
const entry = authorityFor(request.locator);
|
|
351
445
|
if (entry === undefined)
|
|
352
446
|
return { kind: 'room-not-readable', locator: request.locator };
|
|
353
|
-
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);
|
|
354
456
|
if (mapped.kind === 'unmapped')
|
|
355
457
|
return rejectedMapping(mapped);
|
|
356
|
-
const head = operationHead(
|
|
458
|
+
const head = operationHead(batch);
|
|
357
459
|
const delta = deltaBody(mapped.changes);
|
|
358
460
|
const bytes = deltaBytes(delta);
|
|
359
461
|
const handed = request.upload;
|
|
360
|
-
const
|
|
361
|
-
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);
|
|
362
465
|
if (handed === undefined || !lapsedGrant(outcome))
|
|
363
|
-
return
|
|
364
|
-
return
|
|
466
|
+
return settle(outcome);
|
|
467
|
+
return settle(await answer(undefined));
|
|
365
468
|
},
|
|
366
469
|
async history(_authorization, request) {
|
|
367
470
|
const entry = authorityFor(request.locator);
|
|
@@ -379,35 +482,45 @@ export function createAgentsHouseAdapter(options) {
|
|
|
379
482
|
});
|
|
380
483
|
},
|
|
381
484
|
async binary(_authorization, request) {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
}
|
|
391
|
-
|
|
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);
|
|
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);
|
|
402
495
|
},
|
|
403
|
-
|
|
404
|
-
const
|
|
405
|
-
if (
|
|
406
|
-
return
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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;
|
|
411
524
|
},
|
|
412
525
|
reached(locator, version, target) {
|
|
413
526
|
return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
|
|
@@ -418,7 +531,19 @@ export function createAgentsHouseAdapter(options) {
|
|
|
418
531
|
async action(_authorization, request) {
|
|
419
532
|
return withAt(await houseAction(transport, request));
|
|
420
533
|
},
|
|
421
|
-
roomStream: {
|
|
534
|
+
roomStream: {
|
|
535
|
+
hold: held,
|
|
536
|
+
reopen: notifications.reopen,
|
|
537
|
+
observe(notify, receipts, revisions) {
|
|
538
|
+
heldRevisions = revisions;
|
|
539
|
+
return notifications.observe(notify, receipts);
|
|
540
|
+
},
|
|
541
|
+
published(locator, roomVersion) {
|
|
542
|
+
const at = logPosition(roomVersion);
|
|
543
|
+
if (at !== null)
|
|
544
|
+
notifications.published(locator, at);
|
|
545
|
+
},
|
|
546
|
+
},
|
|
422
547
|
conversations: notifications.conversations,
|
|
423
548
|
serviceMode: notifications.serviceMode,
|
|
424
549
|
};
|