@agentshouse/mdruntime-adapter 0.1.0-cd59fd8945dcb → 0.1.0-cd673d02b6e50
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 +13 -7
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +37 -114
- package/dist/adapter.d.ts +1 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +330 -130
- 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 +44 -36
- 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 +486 -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 +72 -23
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js +352 -150
- 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 +125 -0
- package/dist/site.d.ts +1 -1
- package/dist/site.d.ts.map +1 -1
- package/dist/site.js +123 -61
- package/dist/stream.d.ts +5 -4
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +356 -43
- package/dist/transport.d.ts +71 -8
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +4 -0
- 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 +17 -9
- package/dist/write.d.ts.map +1 -1
- package/dist/write.js +138 -40
- package/package.json +8 -5
- package/AGENTS.md +0 -290
- package/dist/links.d.ts +0 -20
- package/dist/links.d.ts.map +0 -1
- package/dist/links.js +0 -104
package/dist/adapter.js
CHANGED
|
@@ -1,77 +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_REVISIONS, } from './transport.js';
|
|
11
|
-
import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
|
|
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';
|
|
12
16
|
import { followsVersion, logPosition, presented, reachedVersion, roomVersion, } from './version.js';
|
|
13
17
|
const UNCERTAIN = { kind: 'transport-uncertainty' };
|
|
14
18
|
const UNCERTAIN_READ = { kind: 'transport-uncertainty' };
|
|
19
|
+
const OCTET_STREAM = 'application/octet-stream';
|
|
20
|
+
const EXPIRED_ADDRESS = 403;
|
|
15
21
|
function lapsedGrant(outcome) {
|
|
16
22
|
return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
|
|
17
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
|
+
}
|
|
18
50
|
export function createAgentsHouseAdapter(options) {
|
|
19
51
|
if ('site' in options)
|
|
20
|
-
return createSiteAdapter(options.site);
|
|
52
|
+
return createSiteAdapter(options.site, options.snapshot === true);
|
|
21
53
|
const transport = options.transport;
|
|
22
54
|
const notifications = houseNotifications(transport, 'credential');
|
|
23
55
|
const tracked = new Map();
|
|
24
56
|
const handed = new Map();
|
|
57
|
+
const rooms = roomStreams('credential');
|
|
58
|
+
const unreused = new Set();
|
|
59
|
+
let heldRevisions;
|
|
25
60
|
let bounds;
|
|
26
61
|
function declared() {
|
|
27
62
|
if (bounds === undefined)
|
|
28
63
|
throw new Error('no Discovery declared the bounds');
|
|
29
64
|
return bounds;
|
|
30
65
|
}
|
|
31
|
-
async function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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));
|
|
37
77
|
else
|
|
38
78
|
handed.delete(entry.locator);
|
|
39
79
|
notifications.reach(entry.authority, scope);
|
|
40
|
-
const seen = [
|
|
41
|
-
...(continued?.seen ?? []),
|
|
42
|
-
...entry.schemas.filter((schema) => scope.files.some((file) => file.path === schema)),
|
|
43
|
-
];
|
|
44
|
-
const models = await partModels(scope, entry, new Set(seen), next === null);
|
|
45
|
-
const continuation = next === null ? null : { after: next, seen };
|
|
46
80
|
return {
|
|
47
|
-
...
|
|
48
|
-
...(models === undefined ? {} : { models }),
|
|
49
|
-
...(
|
|
81
|
+
...(await stepped(bootstrapping(scope))),
|
|
82
|
+
...(part.models === undefined ? {} : { models: part.models }),
|
|
83
|
+
...(part.pageToken === undefined ? {} : { pageToken: part.pageToken }),
|
|
84
|
+
bytes: part.bytes,
|
|
50
85
|
};
|
|
51
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
|
+
}
|
|
52
118
|
async function enumeratedRoom(enumeration, ask) {
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
119
|
+
const first = viewOf(enumeration, ask.entry);
|
|
120
|
+
if (first === undefined)
|
|
55
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;
|
|
56
125
|
notifications.reach(ask.entry.authority, view);
|
|
57
|
-
const answer = refreshFrom(view, ask.fromRoomVersion);
|
|
126
|
+
const answer = await refreshFrom(view, ask.fromRoomVersion);
|
|
58
127
|
if (answer.kind !== 'net-advancement')
|
|
59
128
|
return answer;
|
|
60
|
-
handed.set(ask.entry.locator,
|
|
129
|
+
handed.set(ask.entry.locator, roomVersion(view));
|
|
61
130
|
const models = await viewModels(view, ask.entry);
|
|
62
131
|
return models === undefined ? answer : { ...answer, models };
|
|
63
132
|
}
|
|
64
133
|
async function streamed(entry, request, from) {
|
|
65
|
-
|
|
66
|
-
if (links?.roomVersion !== request.fromRoomVersion)
|
|
134
|
+
if (handed.get(entry.locator) !== request.fromRoomVersion)
|
|
67
135
|
return null;
|
|
68
|
-
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));
|
|
69
141
|
if (advanced === null)
|
|
70
142
|
return null;
|
|
71
|
-
|
|
72
|
-
if (relinked === null)
|
|
73
|
-
return null;
|
|
74
|
-
handed.set(entry.locator, relinked);
|
|
143
|
+
handed.set(entry.locator, advanced.page.roomVersion);
|
|
75
144
|
const models = await streamedModels(advanced.schemas, entry, 'credential');
|
|
76
145
|
return models === undefined ? advanced.page : { ...advanced.page, models };
|
|
77
146
|
}
|
|
@@ -81,28 +150,31 @@ export function createAgentsHouseAdapter(options) {
|
|
|
81
150
|
const at = logPosition(room.roomVersion);
|
|
82
151
|
if (entry === undefined || at === null)
|
|
83
152
|
return [];
|
|
84
|
-
handed.set(room.locator,
|
|
153
|
+
handed.set(room.locator, room.roomVersion);
|
|
85
154
|
notifications.reach(entry.authority, at);
|
|
86
155
|
return [presented(entry.authority, at)];
|
|
87
156
|
});
|
|
88
157
|
notifications.send(kept);
|
|
89
158
|
}
|
|
90
|
-
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) {
|
|
91
166
|
if (outcome.kind !== 'accepted')
|
|
92
167
|
return outcome;
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const at = logPosition(accepted.roomVersion);
|
|
168
|
+
const held = handed.get(entry.locator);
|
|
169
|
+
if (held === undefined)
|
|
170
|
+
return outcome;
|
|
171
|
+
const follows = followsVersion(held, outcome.roomVersion);
|
|
172
|
+
if (follows)
|
|
173
|
+
handed.set(entry.locator, outcome.roomVersion);
|
|
174
|
+
const at = logPosition(outcome.roomVersion);
|
|
101
175
|
if (follows && at !== null)
|
|
102
176
|
notifications.reach(entry.authority, at);
|
|
103
|
-
return
|
|
104
|
-
? accepted
|
|
105
|
-
: { ...accepted, attachments: relinked.attachments };
|
|
177
|
+
return outcome;
|
|
106
178
|
}
|
|
107
179
|
function authorityFor(locator) {
|
|
108
180
|
return tracked.get(locator);
|
|
@@ -121,101 +193,52 @@ export function createAgentsHouseAdapter(options) {
|
|
|
121
193
|
return result;
|
|
122
194
|
return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
|
|
123
195
|
}
|
|
124
|
-
function weight(entry) {
|
|
125
|
-
return contentPrefixes(entry).length;
|
|
126
|
-
}
|
|
127
|
-
async function bootstrapped(entries, answer) {
|
|
128
|
-
let open = entries.map((entry, ask) => ({ entry, ask }));
|
|
129
|
-
while (open.length > 0) {
|
|
130
|
-
const unreached = [];
|
|
131
|
-
for (const part of selections(open, ({ entry }) => weight(entry), declared().max_prefixes)) {
|
|
132
|
-
const answered = await transport.door(DOOR_BOOTSTRAP, {
|
|
133
|
-
prefixes: part.flatMap(({ entry }) => contentPrefixes(entry)),
|
|
134
|
-
});
|
|
135
|
-
const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
|
|
136
|
-
const envelope = classified[0] === null ? parseEnvelope(answered.body) : null;
|
|
137
|
-
for (const [index, asked] of part.entries()) {
|
|
138
|
-
const failure = classified[index];
|
|
139
|
-
if (failure !== null && failure !== undefined) {
|
|
140
|
-
answer(asked.ask, failure);
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
if (envelope === null) {
|
|
144
|
-
answer(asked.ask, UNCERTAIN_READ);
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
const scope = envelope.scopes.find((candidate) => matchesScope(candidate, asked.entry));
|
|
148
|
-
if (scope === undefined && envelope.next !== undefined) {
|
|
149
|
-
unreached.push(asked);
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
answer(asked.ask, await bootstrappedRoom(envelope, scope, asked.entry, null));
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
open = unreached;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
async function continuedBootstrap(entry, continued) {
|
|
159
|
-
const after = { room: entry.authority, path: continued.after };
|
|
160
|
-
const answered = await transport.door(DOOR_BOOTSTRAP, {
|
|
161
|
-
prefixes: contentPrefixes(entry),
|
|
162
|
-
after,
|
|
163
|
-
});
|
|
164
|
-
const failure = classifyDoor(answered.status, answered.body, entry.locator);
|
|
165
|
-
if (failure !== null)
|
|
166
|
-
return failure;
|
|
167
|
-
const envelope = parseEnvelope(answered.body);
|
|
168
|
-
if (envelope === null)
|
|
169
|
-
return UNCERTAIN_READ;
|
|
170
|
-
const scope = envelope.scopes.find((candidate) => matchesScope(candidate, entry));
|
|
171
|
-
return bootstrappedRoom(envelope, scope, entry, continued);
|
|
172
|
-
}
|
|
173
196
|
function layered(asks) {
|
|
174
197
|
const depths = new Map();
|
|
175
198
|
const layers = [];
|
|
176
199
|
for (const ask of asks) {
|
|
177
|
-
const depth = depths.get(ask.entry.authority) ?? 0;
|
|
178
|
-
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);
|
|
179
202
|
(layers[depth] ??= []).push(ask);
|
|
180
203
|
}
|
|
181
204
|
return layers;
|
|
182
205
|
}
|
|
183
206
|
async function enumerated(asks) {
|
|
184
|
-
const answers =
|
|
185
|
-
const parts = layered(asks
|
|
207
|
+
const answers = [];
|
|
208
|
+
const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, declared().max_prefixes));
|
|
186
209
|
for (const part of parts) {
|
|
187
210
|
let open = [...part];
|
|
188
211
|
while (open.length > 0) {
|
|
189
212
|
const answer = await transport.door(DOOR_ENUMERATE, {
|
|
190
|
-
prefixes: open.
|
|
191
|
-
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)),
|
|
192
215
|
});
|
|
193
|
-
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));
|
|
194
217
|
if (classified[0]?.kind === 'beyond-horizon') {
|
|
195
218
|
const named = doorRefusal(answer.body)?.fields.authority;
|
|
196
|
-
const expired = open.filter((ask) => ask.entry.authority === named);
|
|
219
|
+
const expired = open.filter(({ ask }) => ask.entry.authority === named);
|
|
197
220
|
const answered = expired.length === 0 ? UNCERTAIN_READ : { kind: 'beyond-horizon' };
|
|
198
221
|
const gone = expired.length === 0 ? open : expired;
|
|
199
222
|
for (const ask of gone)
|
|
200
|
-
answers.
|
|
223
|
+
answers[ask.index] = answered;
|
|
201
224
|
open = open.filter((ask) => !gone.includes(ask));
|
|
202
225
|
continue;
|
|
203
226
|
}
|
|
204
227
|
const enumeration = classified[0] === null ? parseEnumeration(answer.body, 'credential') : null;
|
|
205
|
-
for (const [
|
|
206
|
-
const failure = classified[
|
|
228
|
+
for (const [answerIndex, { ask, index }] of open.entries()) {
|
|
229
|
+
const failure = classified[answerIndex];
|
|
207
230
|
if (failure !== null && failure !== undefined) {
|
|
208
|
-
answers
|
|
231
|
+
answers[index] = failure;
|
|
209
232
|
continue;
|
|
210
233
|
}
|
|
211
|
-
answers
|
|
234
|
+
answers[index] =
|
|
235
|
+
enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask);
|
|
212
236
|
}
|
|
213
237
|
open = [];
|
|
214
238
|
}
|
|
215
239
|
}
|
|
216
|
-
return
|
|
240
|
+
return answers;
|
|
217
241
|
}
|
|
218
|
-
const bootstrapTogether = gatheredEach(bootstrapped);
|
|
219
242
|
const enumerateTogether = gathered(enumerated);
|
|
220
243
|
async function uploaded(head, changes, delta, bytes, entry, handed, keep) {
|
|
221
244
|
const paths = batchPaths(changes);
|
|
@@ -225,10 +248,12 @@ export function createAgentsHouseAdapter(options) {
|
|
|
225
248
|
}
|
|
226
249
|
const minted = await transport.door(DOOR_BATCH_UPLOAD, { ...head, paths, bytes, sha256 });
|
|
227
250
|
if (minted.status !== 200) {
|
|
228
|
-
return {
|
|
251
|
+
return {
|
|
252
|
+
outcome: await stepped(mutationOutcome(minted.status, minted.body, entry.locator, entry.handle)),
|
|
253
|
+
};
|
|
229
254
|
}
|
|
230
255
|
const grant = parseGrant(minted.body);
|
|
231
|
-
if (grant === null
|
|
256
|
+
if (grant === null)
|
|
232
257
|
return { outcome: UNCERTAIN };
|
|
233
258
|
const delivered = await transport.transfer({
|
|
234
259
|
act: 'authored_batch_upload',
|
|
@@ -238,13 +263,31 @@ export function createAgentsHouseAdapter(options) {
|
|
|
238
263
|
if (delivered.status !== 200)
|
|
239
264
|
return { outcome: UNCERTAIN };
|
|
240
265
|
const holding = parseGrant(delivered.body);
|
|
241
|
-
if (holding === null
|
|
266
|
+
if (holding === null)
|
|
242
267
|
return { outcome: UNCERTAIN };
|
|
243
268
|
const upload = { url: holding.url, operation: holding.operation, bytes, sha256 };
|
|
244
269
|
await keep(upload);
|
|
245
270
|
return { body: { ...head, paths, upload, runtime: notifications.runtime } };
|
|
246
271
|
}
|
|
247
|
-
async function
|
|
272
|
+
async function staged(answer, { changeBatch: batch, file }) {
|
|
273
|
+
const refusal = doorRefusal(answer.body);
|
|
274
|
+
if (refusal?.code !== 'files_unstaged')
|
|
275
|
+
return null;
|
|
276
|
+
const uploads = refusal.fields.upload;
|
|
277
|
+
if (!Array.isArray(uploads))
|
|
278
|
+
return false;
|
|
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)
|
|
283
|
+
return false;
|
|
284
|
+
const finished = await finishedUpload(transport, upload, await file(change.localId));
|
|
285
|
+
if (finished?.body?.staged !== true)
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
async function submit(request, head, changes, delta, bytes, entry, handed, keep, into) {
|
|
248
291
|
const inline = { ...head, changes, runtime: notifications.runtime };
|
|
249
292
|
const prepared = uploadable(inline, bytes, declared())
|
|
250
293
|
? await uploaded(head, changes, delta, bytes, entry, handed, keep)
|
|
@@ -252,14 +295,94 @@ export function createAgentsHouseAdapter(options) {
|
|
|
252
295
|
if ('outcome' in prepared)
|
|
253
296
|
return prepared.outcome;
|
|
254
297
|
const answer = await transport.door(DOOR_BATCH, prepared.body);
|
|
255
|
-
|
|
298
|
+
const stagedFiles = await staged(answer, request);
|
|
299
|
+
if (stagedFiles === false)
|
|
300
|
+
return UNCERTAIN;
|
|
301
|
+
const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
|
|
302
|
+
if (stagedFiles === true && (await staged(settled, request)) !== null)
|
|
303
|
+
return UNCERTAIN;
|
|
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
|
+
}));
|
|
310
|
+
}
|
|
311
|
+
function addressed(answer, locator) {
|
|
312
|
+
const failure = classifyDoor(answer.status, answer.body, locator);
|
|
313
|
+
if (failure !== null) {
|
|
314
|
+
return failure.kind === 'scoped-refusal'
|
|
315
|
+
? {
|
|
316
|
+
kind: 'rejected',
|
|
317
|
+
code: failure.code,
|
|
318
|
+
fields: failure.fields,
|
|
319
|
+
retryable: failure.retryable,
|
|
320
|
+
}
|
|
321
|
+
: failure;
|
|
322
|
+
}
|
|
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)
|
|
357
|
+
return UNCERTAIN_READ;
|
|
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
|
+
});
|
|
256
374
|
}
|
|
257
375
|
return {
|
|
376
|
+
roomTypes: HOUSE_ROOM_TYPES,
|
|
377
|
+
nameableRoomTypes: HOUSE_NAMEABLE_ROOM_TYPES,
|
|
258
378
|
async authorize() {
|
|
259
379
|
return options.authorize();
|
|
260
380
|
},
|
|
261
|
-
async discover(_authorization) {
|
|
262
|
-
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
|
+
});
|
|
263
386
|
const failed = discoveryFailure(answer.status, answer.body);
|
|
264
387
|
if (failed !== null)
|
|
265
388
|
return failed;
|
|
@@ -268,26 +391,40 @@ export function createAgentsHouseAdapter(options) {
|
|
|
268
391
|
return { kind: 'transport-uncertainty' };
|
|
269
392
|
bounds = document.bounds;
|
|
270
393
|
const entries = trackedFrom(document);
|
|
271
|
-
|
|
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);
|
|
272
409
|
for (const entry of entries)
|
|
273
410
|
tracked.set(entry.locator, entry);
|
|
274
411
|
for (const locator of handed.keys())
|
|
275
412
|
if (!tracked.has(locator))
|
|
276
413
|
handed.delete(locator);
|
|
277
|
-
notifications.track(
|
|
414
|
+
notifications.track([...tracked.values()]);
|
|
278
415
|
return {
|
|
279
|
-
...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document)),
|
|
416
|
+
...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions, [...tracked.values()])),
|
|
280
417
|
member: document.member,
|
|
418
|
+
...(covered === null ? {} : { covered }),
|
|
281
419
|
};
|
|
282
420
|
},
|
|
283
421
|
async bootstrap(_authorization, request) {
|
|
284
422
|
const entry = authorityFor(request.locator);
|
|
285
423
|
if (entry === undefined)
|
|
286
424
|
return { kind: 'room-not-readable', locator: request.locator };
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
return bootstrapTogether(entry);
|
|
425
|
+
return request.pageToken === undefined
|
|
426
|
+
? openedRoom(entry, request)
|
|
427
|
+
: streamedPage(entry, request.pageToken, request.held);
|
|
291
428
|
},
|
|
292
429
|
async refresh(_authorization, request) {
|
|
293
430
|
const entry = authorityFor(request.locator);
|
|
@@ -307,17 +444,27 @@ export function createAgentsHouseAdapter(options) {
|
|
|
307
444
|
const entry = authorityFor(request.locator);
|
|
308
445
|
if (entry === undefined)
|
|
309
446
|
return { kind: 'room-not-readable', locator: request.locator };
|
|
310
|
-
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);
|
|
311
456
|
if (mapped.kind === 'unmapped')
|
|
312
457
|
return rejectedMapping(mapped);
|
|
313
|
-
const head = operationHead(
|
|
458
|
+
const head = operationHead(batch);
|
|
314
459
|
const delta = deltaBody(mapped.changes);
|
|
315
460
|
const bytes = deltaBytes(delta);
|
|
316
461
|
const handed = request.upload;
|
|
317
|
-
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);
|
|
318
465
|
if (handed === undefined || !lapsedGrant(outcome))
|
|
319
|
-
return
|
|
320
|
-
return
|
|
466
|
+
return settle(outcome);
|
|
467
|
+
return settle(await answer(undefined));
|
|
321
468
|
},
|
|
322
469
|
async history(_authorization, request) {
|
|
323
470
|
const entry = authorityFor(request.locator);
|
|
@@ -334,6 +481,47 @@ export function createAgentsHouseAdapter(options) {
|
|
|
334
481
|
...(request.revision === undefined ? {} : { revision: request.revision }),
|
|
335
482
|
});
|
|
336
483
|
},
|
|
484
|
+
async binary(_authorization, request) {
|
|
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);
|
|
495
|
+
},
|
|
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;
|
|
524
|
+
},
|
|
337
525
|
reached(locator, version, target) {
|
|
338
526
|
return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
|
|
339
527
|
},
|
|
@@ -343,7 +531,19 @@ export function createAgentsHouseAdapter(options) {
|
|
|
343
531
|
async action(_authorization, request) {
|
|
344
532
|
return withAt(await houseAction(transport, request));
|
|
345
533
|
},
|
|
346
|
-
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
|
+
},
|
|
347
547
|
conversations: notifications.conversations,
|
|
348
548
|
serviceMode: notifications.serviceMode,
|
|
349
549
|
};
|
|
@@ -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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crockford.d.ts","sourceRoot":"","sources":["../src/crockford.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,qCAAqC,CAAC;AAErE,eAAO,MAAM,mBAAmB,uCAA4B,CAAC;AAE7D,eAAO,MAAM,2BAA2B,QAAqC,CAAC"}
|