@agentshouse/mdruntime-adapter 0.1.0-cd028205a5103 → 0.1.0-cd61dfca5d699
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/AGENTS.md +277 -222
- package/dist/actions.d.ts +62 -1
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +156 -133
- package/dist/adapter.d.ts +1 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +317 -93
- package/dist/discovery.d.ts +4 -2
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +22 -7
- package/dist/gather.d.ts +1 -0
- package/dist/gather.d.ts.map +1 -1
- package/dist/gather.js +11 -6
- package/dist/handed.d.ts +15 -0
- package/dist/handed.d.ts.map +1 -0
- package/dist/handed.js +38 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/model.d.ts +1 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +6 -2
- package/dist/read.d.ts +34 -7
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js +156 -77
- package/dist/referrer.d.ts +7 -0
- package/dist/referrer.d.ts.map +1 -0
- package/dist/referrer.js +35 -0
- package/dist/refusal.d.ts +2 -5
- package/dist/refusal.d.ts.map +1 -1
- package/dist/refusal.js +6 -9
- package/dist/site.d.ts +1 -1
- package/dist/site.d.ts.map +1 -1
- package/dist/site.js +182 -49
- package/dist/stream.d.ts +3 -2
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +326 -16
- package/dist/transport.d.ts +64 -10
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +3 -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 +3 -9
- package/dist/write.d.ts.map +1 -1
- package/dist/write.js +92 -14
- package/package.json +3 -4
- 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,23 +1,52 @@
|
|
|
1
1
|
import { DISCOVER_PREFIXES, capabilities, discoveredRoom, mapDiscovery, parseDiscovery, trackedFrom, } from './discovery.js';
|
|
2
|
-
import {
|
|
3
|
-
import { gathered, selections } from './gather.js';
|
|
2
|
+
import { partModels, streamedModels, viewModels } from './model.js';
|
|
3
|
+
import { gathered, gatheredEach, selections } from './gather.js';
|
|
4
4
|
import { createSiteAdapter } from './site.js';
|
|
5
|
-
import { bootstrapOf, contentPrefixes, matchesScope, parseEnumeration, parseEnvelope, parseGrant, refreshFrom, viewOf, } from './read.js';
|
|
6
|
-
import {
|
|
5
|
+
import { bootstrapOf, contentPrefixes, matchesScope, parseEnumeration, parseEnvelope, parseGrant, refreshFrom, viewOf, wholeView, } from './read.js';
|
|
6
|
+
import { sha256Hex } from './digest.js';
|
|
7
|
+
import { acceptedHanded, enumeratedHanded, heldHanded, scopeHanded, streamedHanded, } from './handed.js';
|
|
7
8
|
import { houseAction } from './actions.js';
|
|
8
9
|
import { classifyDoor, classifyRead, discoveryFailure, doorRefusal } from './refusal.js';
|
|
9
10
|
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 { DOOR_BATCH, DOOR_BATCH_UPLOAD, DOOR_BOOTSTRAP, DOOR_DISCOVER, DOOR_ENUMERATE, DOOR_FILES_GET, DOOR_REVISIONS, } from './transport.js';
|
|
11
12
|
import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
|
|
13
|
+
import { houseReferrer } from './referrer.js';
|
|
12
14
|
import { followsVersion, logPosition, presented, reachedVersion, roomVersion, } from './version.js';
|
|
13
15
|
const UNCERTAIN = { kind: 'transport-uncertainty' };
|
|
14
16
|
const UNCERTAIN_READ = { kind: 'transport-uncertainty' };
|
|
15
17
|
function lapsedGrant(outcome) {
|
|
16
18
|
return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
|
|
17
19
|
}
|
|
20
|
+
function heldFiles(authority, held, after, budget) {
|
|
21
|
+
if (held === undefined || budget === 0)
|
|
22
|
+
return null;
|
|
23
|
+
const paths = Object.keys(held.revisions)
|
|
24
|
+
.filter((path) => held.documents[path] !== undefined && (after === null || path > after))
|
|
25
|
+
.sort();
|
|
26
|
+
if (paths.length === 0)
|
|
27
|
+
return null;
|
|
28
|
+
const files = paths.slice(0, budget).map((path) => ({ path, revision: held.revisions[path] }));
|
|
29
|
+
return { room: authority, through: paths.length > budget ? files.at(-1).path : null, files };
|
|
30
|
+
}
|
|
31
|
+
async function reused(scope, held) {
|
|
32
|
+
const sent = new Set(scope.contents.map((entry) => entry.path));
|
|
33
|
+
const contents = [...scope.contents];
|
|
34
|
+
for (const file of scope.files) {
|
|
35
|
+
if (file.binary !== undefined || sent.has(file.path))
|
|
36
|
+
continue;
|
|
37
|
+
const content = held?.documents[file.path];
|
|
38
|
+
if (content === undefined ||
|
|
39
|
+
held?.revisions[file.path] !== file.revision ||
|
|
40
|
+
(await sha256Hex(content)) !== file.sha256) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
contents.push({ path: file.path, content });
|
|
44
|
+
}
|
|
45
|
+
return { ...scope, contents };
|
|
46
|
+
}
|
|
18
47
|
export function createAgentsHouseAdapter(options) {
|
|
19
48
|
if ('site' in options)
|
|
20
|
-
return createSiteAdapter(options.site);
|
|
49
|
+
return createSiteAdapter(options.site, options.snapshot === true);
|
|
21
50
|
const transport = options.transport;
|
|
22
51
|
const notifications = houseNotifications(transport, 'credential');
|
|
23
52
|
const tracked = new Map();
|
|
@@ -28,38 +57,80 @@ export function createAgentsHouseAdapter(options) {
|
|
|
28
57
|
throw new Error('no Discovery declared the bounds');
|
|
29
58
|
return bounds;
|
|
30
59
|
}
|
|
31
|
-
async function bootstrappedRoom(envelope,
|
|
32
|
-
const
|
|
33
|
-
if (
|
|
60
|
+
async function bootstrappedRoom(envelope, sent, asked, continued) {
|
|
61
|
+
const { entry } = asked;
|
|
62
|
+
if (sent === undefined)
|
|
34
63
|
return { kind: 'room-not-readable', locator: entry.locator };
|
|
35
|
-
|
|
64
|
+
const scope = await reused(sent, asked.held);
|
|
65
|
+
if (scope === null) {
|
|
66
|
+
return asked.held === undefined
|
|
67
|
+
? UNCERTAIN_READ
|
|
68
|
+
: continued === null
|
|
69
|
+
? bootstrapTogether({ entry, held: undefined })
|
|
70
|
+
: continuedBootstrap({ entry, held: undefined }, continued);
|
|
71
|
+
}
|
|
72
|
+
const cursor = envelope.next?.room === entry.authority ? envelope.next : undefined;
|
|
73
|
+
const next = cursor?.path ?? null;
|
|
74
|
+
if (continued === null && next === null)
|
|
75
|
+
handed.set(entry.locator, scopeHanded(scope));
|
|
76
|
+
else
|
|
77
|
+
handed.delete(entry.locator);
|
|
36
78
|
notifications.reach(entry.authority, scope);
|
|
37
|
-
const
|
|
38
|
-
|
|
79
|
+
const seen = [
|
|
80
|
+
...(continued?.seen ?? []),
|
|
81
|
+
...entry.schemas.filter((schema) => scope.files.some((file) => file.path === schema)),
|
|
82
|
+
];
|
|
83
|
+
const models = await partModels(scope, entry, new Set(seen), next === null);
|
|
84
|
+
const continuation = next === null
|
|
85
|
+
? null
|
|
86
|
+
: { after: next, seen, ...(cursor?.cut === undefined ? {} : { cut: cursor.cut }) };
|
|
87
|
+
return {
|
|
88
|
+
...bootstrapOf(scope),
|
|
89
|
+
...(models === undefined ? {} : { models }),
|
|
90
|
+
...(continuation === null ? {} : { pageToken: JSON.stringify(continuation) }),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async function enumeratedPage(ask, page) {
|
|
94
|
+
const answer = await transport.door(DOOR_ENUMERATE, {
|
|
95
|
+
prefixes: contentPrefixes(ask.entry),
|
|
96
|
+
positions: [
|
|
97
|
+
{
|
|
98
|
+
...presented(ask.entry.authority, { position: page.after, logEpoch: ask.from.logEpoch }),
|
|
99
|
+
page: { through: page.through, read: page.read },
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
});
|
|
103
|
+
const failure = classifyRead(answer.status, answer.body, ask.entry.locator);
|
|
104
|
+
if (failure !== null)
|
|
105
|
+
return failure;
|
|
106
|
+
const enumeration = parseEnumeration(answer.body, 'credential');
|
|
107
|
+
if (enumeration === null)
|
|
108
|
+
return UNCERTAIN_READ;
|
|
109
|
+
return (viewOf(enumeration, ask.entry) ?? { kind: 'room-not-readable', locator: ask.entry.locator });
|
|
39
110
|
}
|
|
40
111
|
async function enumeratedRoom(enumeration, ask) {
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
112
|
+
const first = viewOf(enumeration, ask.entry);
|
|
113
|
+
if (first === undefined)
|
|
43
114
|
return { kind: 'room-not-readable', locator: ask.entry.locator };
|
|
115
|
+
const view = await wholeView(first, (page) => enumeratedPage(ask, page));
|
|
116
|
+
if ('kind' in view)
|
|
117
|
+
return view;
|
|
44
118
|
notifications.reach(ask.entry.authority, view);
|
|
45
119
|
const answer = refreshFrom(view, ask.fromRoomVersion);
|
|
46
120
|
if (answer.kind !== 'net-advancement')
|
|
47
121
|
return answer;
|
|
48
|
-
handed.set(ask.entry.locator,
|
|
122
|
+
handed.set(ask.entry.locator, enumeratedHanded(view, handed.get(ask.entry.locator)));
|
|
49
123
|
const models = await viewModels(view, ask.entry);
|
|
50
124
|
return models === undefined ? answer : { ...answer, models };
|
|
51
125
|
}
|
|
52
126
|
async function streamed(entry, request, from) {
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
127
|
+
const held = handed.get(entry.locator);
|
|
128
|
+
if (held?.roomVersion !== request.fromRoomVersion)
|
|
55
129
|
return null;
|
|
56
|
-
const advanced = notifications.advance(entry, from, (path, revision) =>
|
|
130
|
+
const advanced = notifications.advance(entry, from, (path, revision) => held.revisions.get(path) === revision);
|
|
57
131
|
if (advanced === null)
|
|
58
132
|
return null;
|
|
59
|
-
|
|
60
|
-
if (relinked === null)
|
|
61
|
-
return null;
|
|
62
|
-
handed.set(entry.locator, relinked);
|
|
133
|
+
handed.set(entry.locator, streamedHanded(held, advanced));
|
|
63
134
|
const models = await streamedModels(advanced.schemas, entry, 'credential');
|
|
64
135
|
return models === undefined ? advanced.page : { ...advanced.page, models };
|
|
65
136
|
}
|
|
@@ -69,7 +140,7 @@ export function createAgentsHouseAdapter(options) {
|
|
|
69
140
|
const at = logPosition(room.roomVersion);
|
|
70
141
|
if (entry === undefined || at === null)
|
|
71
142
|
return [];
|
|
72
|
-
handed.set(room.locator,
|
|
143
|
+
handed.set(room.locator, heldHanded(room));
|
|
73
144
|
notifications.reach(entry.authority, at);
|
|
74
145
|
return [presented(entry.authority, at)];
|
|
75
146
|
});
|
|
@@ -78,19 +149,15 @@ export function createAgentsHouseAdapter(options) {
|
|
|
78
149
|
function adopted(entry, outcome) {
|
|
79
150
|
if (outcome.kind !== 'accepted')
|
|
80
151
|
return outcome;
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
handed.set(entry.locator, relinked.links);
|
|
88
|
-
const at = logPosition(accepted.roomVersion);
|
|
152
|
+
const held = handed.get(entry.locator);
|
|
153
|
+
if (held === undefined)
|
|
154
|
+
return outcome;
|
|
155
|
+
const follows = followsVersion(held.roomVersion, outcome.roomVersion);
|
|
156
|
+
handed.set(entry.locator, acceptedHanded(held, outcome, follows ? outcome.roomVersion : held.roomVersion));
|
|
157
|
+
const at = logPosition(outcome.roomVersion);
|
|
89
158
|
if (follows && at !== null)
|
|
90
159
|
notifications.reach(entry.authority, at);
|
|
91
|
-
return
|
|
92
|
-
? accepted
|
|
93
|
-
: { ...accepted, attachments: relinked.attachments };
|
|
160
|
+
return outcome;
|
|
94
161
|
}
|
|
95
162
|
function authorityFor(locator) {
|
|
96
163
|
return tracked.get(locator);
|
|
@@ -109,88 +176,115 @@ export function createAgentsHouseAdapter(options) {
|
|
|
109
176
|
return result;
|
|
110
177
|
return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
|
|
111
178
|
}
|
|
112
|
-
async function bundled(body) {
|
|
113
|
-
const grant = parseGrant(body);
|
|
114
|
-
if (grant === null)
|
|
115
|
-
return body;
|
|
116
|
-
if (grant.method !== 'GET')
|
|
117
|
-
return null;
|
|
118
|
-
const answer = await transport.transfer({
|
|
119
|
-
act: 'local_copy_bundle',
|
|
120
|
-
method: 'GET',
|
|
121
|
-
url: grant.url,
|
|
122
|
-
answer: 'json',
|
|
123
|
-
});
|
|
124
|
-
return answer.status === 200 ? answer.body : null;
|
|
125
|
-
}
|
|
126
179
|
function weight(entry) {
|
|
127
180
|
return contentPrefixes(entry).length;
|
|
128
181
|
}
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
for (const
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
182
|
+
function holding(asks, after) {
|
|
183
|
+
const budget = Math.floor(declared().max_held_files / Math.max(1, asks.filter((ask) => ask.held !== undefined).length));
|
|
184
|
+
return asks.flatMap((ask) => heldFiles(ask.entry.authority, ask.held, after, budget) ?? []);
|
|
185
|
+
}
|
|
186
|
+
async function bootstrapped(asks, answer) {
|
|
187
|
+
let open = asks.map((asked, ask) => ({ entry: asked.entry, asked, ask }));
|
|
188
|
+
while (open.length > 0) {
|
|
189
|
+
const unreached = [];
|
|
190
|
+
for (const part of selections(open, ({ entry }) => weight(entry), declared().max_prefixes)) {
|
|
191
|
+
const held = holding(part.map(({ asked }) => asked), null);
|
|
192
|
+
const answered = await transport.door(DOOR_BOOTSTRAP, {
|
|
193
|
+
prefixes: part.flatMap(({ entry }) => contentPrefixes(entry)),
|
|
194
|
+
...(held.length === 0 ? {} : { held }),
|
|
195
|
+
});
|
|
196
|
+
const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
|
|
197
|
+
const envelope = classified[0] === null ? parseEnvelope(answered.body) : null;
|
|
198
|
+
for (const [index, asked] of part.entries()) {
|
|
199
|
+
const failure = classified[index];
|
|
200
|
+
if (failure !== null && failure !== undefined) {
|
|
201
|
+
answer(asked.ask, failure);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (envelope === null) {
|
|
205
|
+
answer(asked.ask, UNCERTAIN_READ);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const scope = envelope.scopes.find((candidate) => matchesScope(candidate, asked.entry));
|
|
209
|
+
if (scope === undefined && envelope.next !== undefined) {
|
|
210
|
+
unreached.push(asked);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
answer(asked.ask, await bootstrappedRoom(envelope, scope, asked.asked, null));
|
|
142
214
|
}
|
|
143
|
-
answers.push(failure);
|
|
144
215
|
}
|
|
216
|
+
open = unreached;
|
|
145
217
|
}
|
|
146
|
-
|
|
218
|
+
}
|
|
219
|
+
async function continuedBootstrap(asked, continued) {
|
|
220
|
+
const { entry } = asked;
|
|
221
|
+
const after = {
|
|
222
|
+
room: entry.authority,
|
|
223
|
+
path: continued.after,
|
|
224
|
+
...(continued.cut === undefined ? {} : { cut: continued.cut }),
|
|
225
|
+
};
|
|
226
|
+
const held = holding([asked], continued.after);
|
|
227
|
+
const answered = await transport.door(DOOR_BOOTSTRAP, {
|
|
228
|
+
prefixes: contentPrefixes(entry),
|
|
229
|
+
after,
|
|
230
|
+
...(held.length === 0 ? {} : { held }),
|
|
231
|
+
});
|
|
232
|
+
const failure = classifyRead(answered.status, answered.body, entry.locator);
|
|
233
|
+
if (failure !== null)
|
|
234
|
+
return failure;
|
|
235
|
+
const envelope = parseEnvelope(answered.body);
|
|
236
|
+
if (envelope === null)
|
|
237
|
+
return UNCERTAIN_READ;
|
|
238
|
+
const scope = envelope.scopes.find((candidate) => matchesScope(candidate, entry));
|
|
239
|
+
return bootstrappedRoom(envelope, scope, asked, continued);
|
|
147
240
|
}
|
|
148
241
|
function layered(asks) {
|
|
149
242
|
const depths = new Map();
|
|
150
243
|
const layers = [];
|
|
151
244
|
for (const ask of asks) {
|
|
152
|
-
const depth = depths.get(ask.entry.authority) ?? 0;
|
|
153
|
-
depths.set(ask.entry.authority, depth + 1);
|
|
245
|
+
const depth = depths.get(ask.ask.entry.authority) ?? 0;
|
|
246
|
+
depths.set(ask.ask.entry.authority, depth + 1);
|
|
154
247
|
(layers[depth] ??= []).push(ask);
|
|
155
248
|
}
|
|
156
249
|
return layers;
|
|
157
250
|
}
|
|
158
251
|
async function enumerated(asks) {
|
|
159
|
-
const answers =
|
|
160
|
-
const parts = layered(asks).flatMap((layer) => selections(layer, (ask) => weight(ask.entry), declared().max_prefixes));
|
|
252
|
+
const answers = [];
|
|
253
|
+
const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, ({ ask }) => weight(ask.entry), declared().max_prefixes));
|
|
161
254
|
for (const part of parts) {
|
|
162
255
|
let open = [...part];
|
|
163
256
|
while (open.length > 0) {
|
|
164
257
|
const answer = await transport.door(DOOR_ENUMERATE, {
|
|
165
|
-
prefixes: open.flatMap((ask) => contentPrefixes(ask.entry)),
|
|
166
|
-
positions: open.map((ask) => presented(ask.entry.authority, ask.from)),
|
|
258
|
+
prefixes: open.flatMap(({ ask }) => contentPrefixes(ask.entry)),
|
|
259
|
+
positions: open.map(({ ask }) => presented(ask.entry.authority, ask.from)),
|
|
167
260
|
});
|
|
168
|
-
const classified = open.map((ask) => classifyRead(answer.status, answer.body, ask.entry.locator));
|
|
261
|
+
const classified = open.map(({ ask }) => classifyRead(answer.status, answer.body, ask.entry.locator));
|
|
169
262
|
if (classified[0]?.kind === 'beyond-horizon') {
|
|
170
263
|
const named = doorRefusal(answer.body)?.fields.authority;
|
|
171
|
-
const expired = open.filter((ask) => ask.entry.authority === named);
|
|
264
|
+
const expired = open.filter(({ ask }) => ask.entry.authority === named);
|
|
172
265
|
const answered = expired.length === 0 ? UNCERTAIN_READ : { kind: 'beyond-horizon' };
|
|
173
266
|
const gone = expired.length === 0 ? open : expired;
|
|
174
267
|
for (const ask of gone)
|
|
175
|
-
answers.
|
|
268
|
+
answers[ask.index] = answered;
|
|
176
269
|
open = open.filter((ask) => !gone.includes(ask));
|
|
177
270
|
continue;
|
|
178
271
|
}
|
|
179
272
|
const enumeration = classified[0] === null ? parseEnumeration(answer.body, 'credential') : null;
|
|
180
|
-
for (const [
|
|
181
|
-
const failure = classified[
|
|
273
|
+
for (const [answerIndex, { ask, index }] of open.entries()) {
|
|
274
|
+
const failure = classified[answerIndex];
|
|
182
275
|
if (failure !== null && failure !== undefined) {
|
|
183
|
-
answers
|
|
276
|
+
answers[index] = failure;
|
|
184
277
|
continue;
|
|
185
278
|
}
|
|
186
|
-
answers
|
|
279
|
+
answers[index] =
|
|
280
|
+
enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask);
|
|
187
281
|
}
|
|
188
282
|
open = [];
|
|
189
283
|
}
|
|
190
284
|
}
|
|
191
|
-
return
|
|
285
|
+
return answers;
|
|
192
286
|
}
|
|
193
|
-
const bootstrapTogether =
|
|
287
|
+
const bootstrapTogether = gatheredEach(bootstrapped);
|
|
194
288
|
const enumerateTogether = gathered(enumerated);
|
|
195
289
|
async function uploaded(head, changes, delta, bytes, entry, handed, keep) {
|
|
196
290
|
const paths = batchPaths(changes);
|
|
@@ -207,9 +301,7 @@ export function createAgentsHouseAdapter(options) {
|
|
|
207
301
|
return { outcome: UNCERTAIN };
|
|
208
302
|
const delivered = await transport.transfer({
|
|
209
303
|
act: 'authored_batch_upload',
|
|
210
|
-
method: 'POST',
|
|
211
304
|
url: grant.url,
|
|
212
|
-
answer: 'json',
|
|
213
305
|
body: `${grant.operation}\n${delta}`,
|
|
214
306
|
});
|
|
215
307
|
if (delivered.status !== 200)
|
|
@@ -221,7 +313,30 @@ export function createAgentsHouseAdapter(options) {
|
|
|
221
313
|
await keep(upload);
|
|
222
314
|
return { body: { ...head, paths, upload, runtime: notifications.runtime } };
|
|
223
315
|
}
|
|
224
|
-
async function
|
|
316
|
+
async function staged(answer, batch) {
|
|
317
|
+
const refusal = doorRefusal(answer.body);
|
|
318
|
+
if (refusal?.code !== 'files_unstaged')
|
|
319
|
+
return null;
|
|
320
|
+
const grants = refusal.fields.upload;
|
|
321
|
+
if (!Array.isArray(grants))
|
|
322
|
+
return false;
|
|
323
|
+
for (const grant of grants) {
|
|
324
|
+
const { url, operation, sha256 } = grant;
|
|
325
|
+
const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === sha256);
|
|
326
|
+
if (typeof url !== 'string' || typeof operation !== 'string' || change === undefined)
|
|
327
|
+
return false;
|
|
328
|
+
const delivered = await transport.transfer({
|
|
329
|
+
act: 'binary_stage',
|
|
330
|
+
url,
|
|
331
|
+
body: change.blob,
|
|
332
|
+
operation,
|
|
333
|
+
});
|
|
334
|
+
if (delivered.status !== 200)
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
async function submit(batch, head, changes, delta, bytes, entry, handed, keep) {
|
|
225
340
|
const inline = { ...head, changes, runtime: notifications.runtime };
|
|
226
341
|
const prepared = uploadable(inline, bytes, declared())
|
|
227
342
|
? await uploaded(head, changes, delta, bytes, entry, handed, keep)
|
|
@@ -229,14 +344,61 @@ export function createAgentsHouseAdapter(options) {
|
|
|
229
344
|
if ('outcome' in prepared)
|
|
230
345
|
return prepared.outcome;
|
|
231
346
|
const answer = await transport.door(DOOR_BATCH, prepared.body);
|
|
232
|
-
|
|
347
|
+
const stagedFiles = await staged(answer, batch);
|
|
348
|
+
if (stagedFiles === false)
|
|
349
|
+
return UNCERTAIN;
|
|
350
|
+
const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
|
|
351
|
+
if (stagedFiles === true && (await staged(settled, batch)) !== null)
|
|
352
|
+
return UNCERTAIN;
|
|
353
|
+
return mutationOutcome(settled.status, settled.body, entry.locator, entry.handle);
|
|
354
|
+
}
|
|
355
|
+
function granted(answer, locator) {
|
|
356
|
+
const failure = classifyDoor(answer.status, answer.body, locator);
|
|
357
|
+
if (failure !== null) {
|
|
358
|
+
return failure.kind === 'scoped-refusal'
|
|
359
|
+
? {
|
|
360
|
+
kind: 'rejected',
|
|
361
|
+
code: failure.code,
|
|
362
|
+
fields: failure.fields,
|
|
363
|
+
retryable: failure.retryable,
|
|
364
|
+
}
|
|
365
|
+
: failure;
|
|
366
|
+
}
|
|
367
|
+
const grant = parseGrant(answer.body);
|
|
368
|
+
const name = answer.body.name;
|
|
369
|
+
if (grant === null || grant.method !== 'GET' || typeof name !== 'string') {
|
|
370
|
+
return UNCERTAIN_READ;
|
|
371
|
+
}
|
|
372
|
+
return { kind: 'link', url: grant.url, name };
|
|
373
|
+
}
|
|
374
|
+
async function downloaded(answer, locator) {
|
|
375
|
+
const link = granted(answer, locator);
|
|
376
|
+
if (link.kind !== 'link')
|
|
377
|
+
return link;
|
|
378
|
+
const read = await transport.download(link.url);
|
|
379
|
+
if (read.status !== 200 || read.blob === null)
|
|
380
|
+
return UNCERTAIN_READ;
|
|
381
|
+
return { kind: 'binary', blob: read.blob };
|
|
382
|
+
}
|
|
383
|
+
function pathGrant(request) {
|
|
384
|
+
const entry = authorityFor(request.locator);
|
|
385
|
+
if (entry === undefined)
|
|
386
|
+
return null;
|
|
387
|
+
return transport.door(DOOR_FILES_GET, {
|
|
388
|
+
room_ref: entry.authority,
|
|
389
|
+
path: request.path,
|
|
390
|
+
revision: request.revision,
|
|
391
|
+
});
|
|
233
392
|
}
|
|
234
393
|
return {
|
|
235
394
|
async authorize() {
|
|
236
395
|
return options.authorize();
|
|
237
396
|
},
|
|
238
|
-
async discover(_authorization) {
|
|
239
|
-
const answer = await transport.door(DOOR_DISCOVER, {
|
|
397
|
+
async discover(_authorization, scope) {
|
|
398
|
+
const answer = await transport.door(DOOR_DISCOVER, {
|
|
399
|
+
prefixes: [...DISCOVER_PREFIXES],
|
|
400
|
+
...(scope === undefined ? {} : { rooms: scope.authorities }),
|
|
401
|
+
});
|
|
240
402
|
const failed = discoveryFailure(answer.status, answer.body);
|
|
241
403
|
if (failed !== null)
|
|
242
404
|
return failed;
|
|
@@ -245,22 +407,42 @@ export function createAgentsHouseAdapter(options) {
|
|
|
245
407
|
return { kind: 'transport-uncertainty' };
|
|
246
408
|
bounds = document.bounds;
|
|
247
409
|
const entries = trackedFrom(document);
|
|
248
|
-
|
|
410
|
+
const covered = scope === undefined
|
|
411
|
+
? null
|
|
412
|
+
: [
|
|
413
|
+
...new Set([
|
|
414
|
+
...[...tracked.values()]
|
|
415
|
+
.filter((entry) => scope.authorities.includes(entry.authority))
|
|
416
|
+
.map((entry) => entry.locator),
|
|
417
|
+
...entries.map((entry) => entry.locator),
|
|
418
|
+
]),
|
|
419
|
+
];
|
|
420
|
+
if (covered === null)
|
|
421
|
+
tracked.clear();
|
|
422
|
+
else
|
|
423
|
+
for (const locator of covered)
|
|
424
|
+
tracked.delete(locator);
|
|
249
425
|
for (const entry of entries)
|
|
250
426
|
tracked.set(entry.locator, entry);
|
|
251
427
|
for (const locator of handed.keys())
|
|
252
428
|
if (!tracked.has(locator))
|
|
253
429
|
handed.delete(locator);
|
|
254
|
-
notifications.track(
|
|
255
|
-
return
|
|
430
|
+
notifications.track([...tracked.values()]);
|
|
431
|
+
return {
|
|
432
|
+
...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions, [...tracked.values()])),
|
|
433
|
+
member: document.member,
|
|
434
|
+
...(covered === null ? {} : { covered }),
|
|
435
|
+
};
|
|
256
436
|
},
|
|
257
437
|
async bootstrap(_authorization, request) {
|
|
258
438
|
const entry = authorityFor(request.locator);
|
|
259
439
|
if (entry === undefined)
|
|
260
440
|
return { kind: 'room-not-readable', locator: request.locator };
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
441
|
+
const asked = { entry, held: request.held };
|
|
442
|
+
if (request.pageToken !== undefined) {
|
|
443
|
+
return continuedBootstrap(asked, JSON.parse(request.pageToken));
|
|
444
|
+
}
|
|
445
|
+
return bootstrapTogether(asked);
|
|
264
446
|
},
|
|
265
447
|
async refresh(_authorization, request) {
|
|
266
448
|
const entry = authorityFor(request.locator);
|
|
@@ -287,10 +469,11 @@ export function createAgentsHouseAdapter(options) {
|
|
|
287
469
|
const delta = deltaBody(mapped.changes);
|
|
288
470
|
const bytes = deltaBytes(delta);
|
|
289
471
|
const handed = request.upload;
|
|
290
|
-
const
|
|
472
|
+
const batch = request.changeBatch;
|
|
473
|
+
const outcome = await submit(batch, head, mapped.changes, delta, bytes, entry, handed, keep);
|
|
291
474
|
if (handed === undefined || !lapsedGrant(outcome))
|
|
292
475
|
return adopted(entry, outcome);
|
|
293
|
-
return adopted(entry, await submit(head, mapped.changes, delta, bytes, entry, undefined, keep));
|
|
476
|
+
return adopted(entry, await submit(batch, head, mapped.changes, delta, bytes, entry, undefined, keep));
|
|
294
477
|
},
|
|
295
478
|
async history(_authorization, request) {
|
|
296
479
|
const entry = authorityFor(request.locator);
|
|
@@ -307,6 +490,38 @@ export function createAgentsHouseAdapter(options) {
|
|
|
307
490
|
...(request.revision === undefined ? {} : { revision: request.revision }),
|
|
308
491
|
});
|
|
309
492
|
},
|
|
493
|
+
async binary(_authorization, request) {
|
|
494
|
+
if (request.kind === 'path') {
|
|
495
|
+
const asked = pathGrant(request);
|
|
496
|
+
if (asked === null)
|
|
497
|
+
return { kind: 'room-not-readable', locator: request.locator };
|
|
498
|
+
return downloaded(await asked, request.locator);
|
|
499
|
+
}
|
|
500
|
+
const referrer = houseReferrer(request.referrer, tracked);
|
|
501
|
+
if (referrer === null) {
|
|
502
|
+
return { kind: 'rejected', code: 'referrer_unknown', fields: {}, retryable: false };
|
|
503
|
+
}
|
|
504
|
+
const answer = await transport.door(DOOR_FILES_GET, {
|
|
505
|
+
file: request.fileRef,
|
|
506
|
+
referrer: referrer.referrer,
|
|
507
|
+
});
|
|
508
|
+
return downloaded(answer, referrer.locator);
|
|
509
|
+
},
|
|
510
|
+
async link(_authorization, request) {
|
|
511
|
+
const asked = pathGrant(request);
|
|
512
|
+
if (asked === null)
|
|
513
|
+
return { kind: 'room-not-readable', locator: request.locator };
|
|
514
|
+
return granted(await asked, request.locator);
|
|
515
|
+
},
|
|
516
|
+
referrerScope(referrer) {
|
|
517
|
+
const held = houseReferrer(referrer, tracked);
|
|
518
|
+
if (held === null)
|
|
519
|
+
return null;
|
|
520
|
+
if (held.locator === '')
|
|
521
|
+
return { kind: 'conversations' };
|
|
522
|
+
const { path } = held.referrer;
|
|
523
|
+
return { kind: 'room', locator: held.locator, ...(path === undefined ? {} : { path }) };
|
|
524
|
+
},
|
|
310
525
|
reached(locator, version, target) {
|
|
311
526
|
return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
|
|
312
527
|
},
|
|
@@ -316,7 +531,16 @@ export function createAgentsHouseAdapter(options) {
|
|
|
316
531
|
async action(_authorization, request) {
|
|
317
532
|
return withAt(await houseAction(transport, request));
|
|
318
533
|
},
|
|
319
|
-
roomStream: {
|
|
534
|
+
roomStream: {
|
|
535
|
+
hold: held,
|
|
536
|
+
reopen: notifications.reopen,
|
|
537
|
+
observe: notifications.observe,
|
|
538
|
+
published(locator, roomVersion) {
|
|
539
|
+
const at = logPosition(roomVersion);
|
|
540
|
+
if (at !== null)
|
|
541
|
+
notifications.published(locator, at);
|
|
542
|
+
},
|
|
543
|
+
},
|
|
320
544
|
conversations: notifications.conversations,
|
|
321
545
|
serviceMode: notifications.serviceMode,
|
|
322
546
|
};
|
package/dist/discovery.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Capability, DiscoveredRoom, Discovery, ProviderLimit, Retention } from '@agentshouse/mdruntime';
|
|
1
|
+
import type { Capability, DiscoveredRoom, Discovery, Member, ProviderLimit, Retention } from '@agentshouse/mdruntime';
|
|
2
2
|
import { type LogPosition } from './version.js';
|
|
3
3
|
export declare const PROVIDER_ACTIONS = "provider-actions";
|
|
4
4
|
export declare const DISCOVER_PREFIXES: readonly string[];
|
|
@@ -13,6 +13,7 @@ export type HouseBounds = {
|
|
|
13
13
|
max_inline_body_bytes: number;
|
|
14
14
|
max_upload_bytes: number;
|
|
15
15
|
max_prefixes: number;
|
|
16
|
+
max_held_files: number;
|
|
16
17
|
change_retention_seconds: number;
|
|
17
18
|
operation_retention_seconds: number;
|
|
18
19
|
};
|
|
@@ -37,6 +38,7 @@ export type HouseAuthority = LogPosition & {
|
|
|
37
38
|
operations: HouseOperations;
|
|
38
39
|
};
|
|
39
40
|
export type HouseDiscoveryDocument = {
|
|
41
|
+
member: Member;
|
|
40
42
|
bounds: HouseBounds;
|
|
41
43
|
authorities: readonly HouseAuthority[];
|
|
42
44
|
actions: readonly string[];
|
|
@@ -64,7 +66,7 @@ export declare function siteTracked(document: HouseSiteDiscovery): TrackedAuthor
|
|
|
64
66
|
export declare function trackedFrom(document: HouseDiscoveryDocument): TrackedAuthority[];
|
|
65
67
|
export declare function providerLimits(bounds: HouseBounds): readonly ProviderLimit[];
|
|
66
68
|
export declare function declaredRetention(bounds: HouseBounds): Retention;
|
|
67
|
-
export declare function capabilities(
|
|
69
|
+
export declare function capabilities(actions: HouseDiscoveryDocument['actions'], rooms: readonly Pick<TrackedAuthority, 'authority' | 'houseModel'>[]): readonly Capability[];
|
|
68
70
|
export declare function siteCapabilities(): readonly Capability[];
|
|
69
71
|
export declare function discoveredRoom(authority: HouseAuthority): DiscoveredRoom;
|
|
70
72
|
export declare function mapDiscovery(document: Pick<HouseDiscoveryDocument, 'bounds'>, rooms: readonly DiscoveredRoom[], declared: readonly Capability[]): Discovery;
|
package/dist/discovery.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,cAAc,EACd,SAAS,EACT,aAAa,EACb,SAAS,EACV,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3E,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAEnD,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAAU,CAAC;AAE1D,MAAM,MAAM,WAAW,GAAG;IACxB,uBAAuB,EAAE,MAAM,CAAC;IAChC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,wBAAwB,EAAE,MAAM,CAAC;IACjC,2BAA2B,EAAE,MAAM,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,kBAAkB,EAAE,UAAU,GAAG,aAAa,CAAC;IAC/C,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,UAAU,EAAE,eAAe,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,SAAS,cAAc,EAAE,CAAC;IACvC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAC5C,CAAC;
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"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"}
|