@agentshouse/mdruntime-adapter 0.1.0-c96ce90a9df53 → 0.1.0-ca218e62303fd
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 +114 -123
- package/dist/actions.d.ts +23 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +34 -30
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +101 -17
- package/dist/discovery.d.ts +1 -0
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +6 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/model.d.ts +0 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +0 -3
- package/dist/read.d.ts +20 -1
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js +80 -4
- package/dist/refusal.d.ts.map +1 -1
- package/dist/refusal.js +2 -1
- package/dist/site.d.ts.map +1 -1
- package/dist/site.js +104 -32
- package/dist/stream.d.ts +3 -2
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +286 -7
- package/dist/transport.d.ts +28 -3
- package/dist/transport.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/write.js +13 -9
- package/package.json +2 -3
package/dist/site.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { actionOutcome } from './actions.js';
|
|
1
|
+
import { actionOutcome, SITE_SUBMIT } from './actions.js';
|
|
2
2
|
import { mapDiscovery, parseSiteDiscovery, siteCapabilities, siteTracked, } from './discovery.js';
|
|
3
|
-
import {
|
|
4
|
-
import { advancedScope, bootstrapOf, parseEnumeration, parseSiteBootstrap, refreshFrom, viewOf, } from './read.js';
|
|
3
|
+
import { partModels, streamedModels, viewModels } from './model.js';
|
|
4
|
+
import { advancedScope, bootstrapOf, parseEnumeration, parseSiteBootstrap, refreshFrom, siteBootstrapNext, siteBootstrapOffset, viewOf, wholeView, } from './read.js';
|
|
5
|
+
import { sha256Hex } from './digest.js';
|
|
5
6
|
import { classifyDoor, classifyRead, discoveryFailure } from './refusal.js';
|
|
6
7
|
import { houseNotifications } from './stream.js';
|
|
7
8
|
import { SITE_DOOR_BOOTSTRAP, SITE_DOOR_DISCOVER, SITE_DOOR_ENUMERATE, SITE_DOOR_SUBMIT, SITE_FILE_RECORD, SITE_FILES, } from './transport.js';
|
|
@@ -29,32 +30,90 @@ export function createSiteAdapter(transport) {
|
|
|
29
30
|
const named = houseReferrer(referrer, tracked);
|
|
30
31
|
return named === null || !tracked.has(named.locator) ? null : named;
|
|
31
32
|
}
|
|
32
|
-
async function
|
|
33
|
+
async function enumerated(entry, from, page) {
|
|
33
34
|
const answer = await transport.door(SITE_DOOR_ENUMERATE, {
|
|
34
|
-
positions: [
|
|
35
|
+
positions: [
|
|
36
|
+
page === undefined
|
|
37
|
+
? presented(entry.authority, from)
|
|
38
|
+
: {
|
|
39
|
+
...presented(entry.authority, { position: page.after, logEpoch: from.logEpoch }),
|
|
40
|
+
page: { through: page.through, read: page.read },
|
|
41
|
+
},
|
|
42
|
+
],
|
|
35
43
|
});
|
|
36
44
|
const classified = classifyRead(answer.status, answer.body, entry.locator);
|
|
37
45
|
if (classified !== null)
|
|
38
46
|
return classified;
|
|
39
|
-
|
|
47
|
+
const found = parseEnumeration(answer.body, 'site');
|
|
48
|
+
if (found === null)
|
|
49
|
+
return UNCERTAIN;
|
|
50
|
+
return viewOf(found, entry) ?? { kind: 'room-not-readable', locator: entry.locator };
|
|
51
|
+
}
|
|
52
|
+
async function enumeration(entry, from) {
|
|
53
|
+
const first = await enumerated(entry, from);
|
|
54
|
+
return 'kind' in first ? first : wholeView(first, (page) => enumerated(entry, from, page));
|
|
40
55
|
}
|
|
41
|
-
async function
|
|
42
|
-
const
|
|
56
|
+
async function bootstrapPart(entry, after, offset) {
|
|
57
|
+
const query = [
|
|
58
|
+
...(after === null ? [] : [`after=${encodeURIComponent(after)}`]),
|
|
59
|
+
...(offset === null ? [] : [`offset=${offset}`]),
|
|
60
|
+
];
|
|
61
|
+
const answer = await transport.read(`${SITE_DOOR_BOOTSTRAP}${entry.authority}${query.length === 0 ? '' : `?${query.join('&')}`}`);
|
|
43
62
|
const classified = classifyDoor(answer.status, answer.body, entry.locator);
|
|
44
63
|
if (classified !== null)
|
|
45
64
|
return { failure: classified };
|
|
46
65
|
const scope = parseSiteBootstrap(answer.body);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
66
|
+
return scope === null ? { failure: UNCERTAIN } : { body: answer.body, scope };
|
|
67
|
+
}
|
|
68
|
+
async function assembled(entry, scope, path, offset) {
|
|
69
|
+
const opened = scope.contents.at(-1);
|
|
70
|
+
if (opened === undefined || opened.path !== path || opened.content.length !== offset) {
|
|
71
|
+
return UNCERTAIN;
|
|
72
|
+
}
|
|
73
|
+
let text = opened.content;
|
|
74
|
+
for (;;) {
|
|
75
|
+
const part = await bootstrapPart(entry, path, text.length);
|
|
76
|
+
if ('failure' in part)
|
|
77
|
+
return part.failure;
|
|
78
|
+
const [piece] = part.scope.contents;
|
|
79
|
+
if (piece === undefined ||
|
|
80
|
+
part.scope.contents.length !== 1 ||
|
|
81
|
+
piece.path !== path ||
|
|
82
|
+
piece.content.length === 0) {
|
|
83
|
+
return UNCERTAIN;
|
|
84
|
+
}
|
|
85
|
+
text += piece.content;
|
|
86
|
+
const more = siteBootstrapOffset(part.body);
|
|
87
|
+
if (more === null)
|
|
88
|
+
break;
|
|
89
|
+
if (more !== text.length)
|
|
90
|
+
return UNCERTAIN;
|
|
91
|
+
}
|
|
92
|
+
const file = scope.files.find((held) => held.path === path);
|
|
93
|
+
if (file === undefined || (await sha256Hex(text)) !== file.version)
|
|
94
|
+
return UNCERTAIN;
|
|
95
|
+
return { ...scope, contents: [...scope.contents.slice(0, -1), { path, content: text }] };
|
|
96
|
+
}
|
|
97
|
+
async function caughtUp(entry, after) {
|
|
98
|
+
const part = await bootstrapPart(entry, after, null);
|
|
99
|
+
if ('failure' in part)
|
|
100
|
+
return part;
|
|
101
|
+
const next = siteBootstrapNext(part.body);
|
|
102
|
+
const offset = siteBootstrapOffset(part.body);
|
|
103
|
+
if (offset !== null) {
|
|
104
|
+
if (next === null)
|
|
105
|
+
return { failure: UNCERTAIN };
|
|
106
|
+
const scope = await assembled(entry, part.scope, next, offset);
|
|
107
|
+
return 'kind' in scope ? { failure: scope } : { scope, next };
|
|
108
|
+
}
|
|
109
|
+
const { scope } = part;
|
|
110
|
+
if (after !== null || next !== null || roomVersion(scope) === roomVersion(entry)) {
|
|
111
|
+
return { scope, next };
|
|
112
|
+
}
|
|
113
|
+
const view = await enumeration(entry, scope);
|
|
114
|
+
if ('kind' in view)
|
|
115
|
+
return { failure: settled(view) };
|
|
116
|
+
return { scope: advancedScope(scope, view), next: null };
|
|
58
117
|
}
|
|
59
118
|
async function refreshed(entry, fromRoomVersion) {
|
|
60
119
|
const from = logPosition(fromRoomVersion);
|
|
@@ -65,12 +124,9 @@ export function createSiteAdapter(transport) {
|
|
|
65
124
|
const models = await streamedModels(streamed.schemas, entry, 'site');
|
|
66
125
|
return models === undefined ? streamed.page : { ...streamed.page, models };
|
|
67
126
|
}
|
|
68
|
-
const
|
|
69
|
-
if ('kind' in
|
|
70
|
-
return
|
|
71
|
-
const view = viewOf(found, entry);
|
|
72
|
-
if (view === undefined)
|
|
73
|
-
return { kind: 'room-not-readable', locator: entry.locator };
|
|
127
|
+
const view = await enumeration(entry, from);
|
|
128
|
+
if ('kind' in view)
|
|
129
|
+
return view;
|
|
74
130
|
notifications.reach(entry.authority, view);
|
|
75
131
|
const answer = refreshFrom(view, fromRoomVersion);
|
|
76
132
|
if (answer.kind !== 'net-advancement')
|
|
@@ -80,6 +136,7 @@ export function createSiteAdapter(transport) {
|
|
|
80
136
|
}
|
|
81
137
|
return {
|
|
82
138
|
localSaves: true,
|
|
139
|
+
exhaustiveDiscovery: true,
|
|
83
140
|
async authorize() {
|
|
84
141
|
return {};
|
|
85
142
|
},
|
|
@@ -102,14 +159,24 @@ export function createSiteAdapter(transport) {
|
|
|
102
159
|
const entry = tracked.get(request.locator);
|
|
103
160
|
if (entry === undefined)
|
|
104
161
|
return { kind: 'room-not-readable', locator: request.locator };
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
162
|
+
const continued = request.pageToken === undefined
|
|
163
|
+
? null
|
|
164
|
+
: JSON.parse(request.pageToken);
|
|
165
|
+
const held = await caughtUp(entry, continued?.after ?? null);
|
|
108
166
|
if ('failure' in held)
|
|
109
167
|
return held.failure;
|
|
110
168
|
notifications.reach(entry.authority, held.scope);
|
|
111
|
-
const
|
|
112
|
-
|
|
169
|
+
const seen = [
|
|
170
|
+
...(continued?.seen ?? []),
|
|
171
|
+
...entry.schemas.filter((schema) => held.scope.files.some((file) => file.path === schema)),
|
|
172
|
+
];
|
|
173
|
+
const models = await partModels(held.scope, entry, new Set(seen), held.next === null);
|
|
174
|
+
const continuation = held.next === null ? null : { after: held.next, seen };
|
|
175
|
+
return {
|
|
176
|
+
...bootstrapOf(held.scope),
|
|
177
|
+
...(models === undefined ? {} : { models }),
|
|
178
|
+
...(continuation === null ? {} : { pageToken: JSON.stringify(continuation) }),
|
|
179
|
+
};
|
|
113
180
|
},
|
|
114
181
|
async refresh(_authorization, request) {
|
|
115
182
|
const entry = tracked.get(request.locator);
|
|
@@ -158,7 +225,7 @@ export function createSiteAdapter(transport) {
|
|
|
158
225
|
return followsVersion(version, next);
|
|
159
226
|
},
|
|
160
227
|
async action(_authorization, request) {
|
|
161
|
-
if (request.name !==
|
|
228
|
+
if (request.name !== SITE_SUBMIT) {
|
|
162
229
|
return {
|
|
163
230
|
kind: 'rejected',
|
|
164
231
|
code: 'action-unimplemented',
|
|
@@ -168,7 +235,7 @@ export function createSiteAdapter(transport) {
|
|
|
168
235
|
}
|
|
169
236
|
if (!transport.online())
|
|
170
237
|
return UNCERTAIN;
|
|
171
|
-
return actionOutcome(await transport.door(SITE_DOOR_SUBMIT, request.input),
|
|
238
|
+
return actionOutcome(await transport.door(SITE_DOOR_SUBMIT, request.input), SITE_SUBMIT);
|
|
172
239
|
},
|
|
173
240
|
roomStream: {
|
|
174
241
|
hold(rooms) {
|
|
@@ -183,6 +250,11 @@ export function createSiteAdapter(transport) {
|
|
|
183
250
|
},
|
|
184
251
|
reopen: notifications.reopen,
|
|
185
252
|
observe: notifications.observe,
|
|
253
|
+
published(locator, roomVersion) {
|
|
254
|
+
const at = logPosition(roomVersion);
|
|
255
|
+
if (at !== null)
|
|
256
|
+
notifications.published(locator, at);
|
|
257
|
+
},
|
|
186
258
|
},
|
|
187
259
|
serviceMode: notifications.serviceMode,
|
|
188
260
|
};
|
package/dist/stream.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConversationStream, RefreshPage, RoomStreamEvent, ServiceModeSignal } from '@agentshouse/mdruntime';
|
|
1
|
+
import type { ConversationStream, RefreshPage, RoomStreamEvent, ServiceModeSignal, StreamReceipts } from '@agentshouse/mdruntime';
|
|
2
2
|
import type { TrackedAuthority } from './discovery.js';
|
|
3
3
|
import type { HouseDoor, HouseHeldRoom, HouseMode, HouseStreamEntry } from './transport.js';
|
|
4
4
|
import { type LogPosition } from './version.js';
|
|
@@ -10,8 +10,9 @@ export type StreamedAdvancement = {
|
|
|
10
10
|
};
|
|
11
11
|
export type HouseNotifications = {
|
|
12
12
|
runtime: string;
|
|
13
|
-
observe(notify: (event: RoomStreamEvent) => void): () => void;
|
|
13
|
+
observe(notify: (event: RoomStreamEvent) => void, receipts: StreamReceipts): () => void;
|
|
14
14
|
send(rooms: readonly HouseHeldRoom[]): void;
|
|
15
|
+
published(locator: string, at: LogPosition): void;
|
|
15
16
|
reopen(): void;
|
|
16
17
|
track(authorities: readonly TrackedAuthority[]): void;
|
|
17
18
|
reach(authority: string, at: LogPosition): void;
|
package/dist/stream.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,kBAAkB,EAElB,WAAW,EACX,eAAe,EAEf,iBAAiB,
|
|
1
|
+
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,kBAAkB,EAElB,WAAW,EACX,eAAe,EAEf,iBAAiB,EACjB,cAAc,EAEf,MAAM,wBAAwB,CAAC;AAGhC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,KAAK,EACV,SAAS,EACT,aAAa,EAEb,SAAS,EAET,gBAAgB,EAEjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AA0C7D,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;AAEvE,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACrC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,EAAE,QAAQ,EAAE,cAAc,GAAG,MAAM,IAAI,CAAC;IACxF,IAAI,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI,CAAC;IAC5C,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI,CAAC;IAClD,MAAM,IAAI,IAAI,CAAC;IACf,KAAK,CAAC,WAAW,EAAE,SAAS,gBAAgB,EAAE,GAAG,IAAI,CAAC;IACtD,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,OAAO,CACL,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAE,WAAW,EACjB,KAAK,CAAC,EAAE,YAAY,GACnB,mBAAmB,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,kBAAkB,CAAC;IAClC,WAAW,EAAE,iBAAiB,CAAC;CAChC,CAAC;AAkFF,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,GAAG,kBAAkB,CAmhB5F"}
|
package/dist/stream.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { acceptedMessage } from './actions.js';
|
|
2
|
+
import { sha256Hex } from './digest.js';
|
|
2
3
|
import { binaryFileOf, headChange } from './read.js';
|
|
3
4
|
import { roomVersion } from './version.js';
|
|
4
5
|
const HELD_FRAMES_MAX = 256;
|
|
@@ -21,9 +22,78 @@ function position(value) {
|
|
|
21
22
|
return null;
|
|
22
23
|
return BigInt(value);
|
|
23
24
|
}
|
|
25
|
+
function assembled(entries) {
|
|
26
|
+
const whole = [];
|
|
27
|
+
let open = null;
|
|
28
|
+
for (const entry of entries) {
|
|
29
|
+
if (entry.kind === 'piece') {
|
|
30
|
+
if (open === null || open.entry.path !== entry.path || entry.text === undefined)
|
|
31
|
+
return null;
|
|
32
|
+
open.texts.push(entry.text);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
if (open !== null)
|
|
36
|
+
return null;
|
|
37
|
+
if (entry.continued !== true) {
|
|
38
|
+
whole.push(entry);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (entry.text === undefined)
|
|
42
|
+
return null;
|
|
43
|
+
open = { entry, texts: [entry.text] };
|
|
44
|
+
}
|
|
45
|
+
if (entry.continued === true)
|
|
46
|
+
continue;
|
|
47
|
+
const { continued: _continued, ...head } = open.entry;
|
|
48
|
+
whole.push({ ...head, text: open.texts.join('') });
|
|
49
|
+
open = null;
|
|
50
|
+
}
|
|
51
|
+
return open === null ? whole : null;
|
|
52
|
+
}
|
|
53
|
+
async function verified(entries) {
|
|
54
|
+
try {
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
if (entry.text === undefined || entry.sha256 === undefined)
|
|
57
|
+
continue;
|
|
58
|
+
if ((await sha256Hex(entry.text)) !== entry.sha256)
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function resumed(kept, frame) {
|
|
68
|
+
const last = frame.entries.at(-1);
|
|
69
|
+
const point = { parts: frame.index + 1, from: frame.from, target: frame.target };
|
|
70
|
+
if (last?.continued !== true)
|
|
71
|
+
return { ...point, handled: frame.handled, piece: null };
|
|
72
|
+
const text = last.text?.length ?? 0;
|
|
73
|
+
if (last.kind !== 'piece') {
|
|
74
|
+
return last.sha256 === undefined
|
|
75
|
+
? kept.resume
|
|
76
|
+
: { ...point, handled: frame.handled, piece: { sha256: last.sha256, offset: text } };
|
|
77
|
+
}
|
|
78
|
+
const piece = kept.resume?.piece ?? null;
|
|
79
|
+
if (piece === null)
|
|
80
|
+
return kept.resume;
|
|
81
|
+
const pieces = frame.entries.reduce((sum, entry) => sum + (entry.kind === 'piece' ? (entry.text?.length ?? 0) : 0), 0);
|
|
82
|
+
return {
|
|
83
|
+
...point,
|
|
84
|
+
handled: frame.handled,
|
|
85
|
+
piece: { sha256: piece.sha256, offset: piece.offset + pieces },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
24
88
|
export function houseNotifications(transport, mode) {
|
|
25
89
|
const stream = transport.stream;
|
|
26
90
|
const lines = new Map();
|
|
91
|
+
const kept = new Map();
|
|
92
|
+
let receipts = null;
|
|
93
|
+
let loaded = Promise.resolve();
|
|
94
|
+
let acknowledging = null;
|
|
95
|
+
let receiving = Promise.resolve();
|
|
96
|
+
let verifying = 0;
|
|
27
97
|
const locators = new Map();
|
|
28
98
|
const observers = new Set();
|
|
29
99
|
const conversationObservers = new Set();
|
|
@@ -49,6 +119,7 @@ export function houseNotifications(transport, mode) {
|
|
|
49
119
|
}
|
|
50
120
|
function reopened(next) {
|
|
51
121
|
opening = next;
|
|
122
|
+
acknowledging = next;
|
|
52
123
|
const gapped = new Set([...replicated, ...live]);
|
|
53
124
|
live.clear();
|
|
54
125
|
for (const conversation of gapped)
|
|
@@ -110,7 +181,162 @@ export function houseNotifications(transport, mode) {
|
|
|
110
181
|
if (line !== undefined)
|
|
111
182
|
lines.set(authority, { ...line, frames: [] });
|
|
112
183
|
}
|
|
113
|
-
function
|
|
184
|
+
async function forget(carries) {
|
|
185
|
+
const held = carries.filter((carry) => kept.delete(carry));
|
|
186
|
+
if (held.length > 0)
|
|
187
|
+
await receipts?.drop(held).catch(() => { });
|
|
188
|
+
}
|
|
189
|
+
async function lost(authority, carry) {
|
|
190
|
+
interrupt(authority);
|
|
191
|
+
await forget([carry]);
|
|
192
|
+
}
|
|
193
|
+
async function stage(frame, via) {
|
|
194
|
+
await loaded;
|
|
195
|
+
const port = receipts;
|
|
196
|
+
if (port === null)
|
|
197
|
+
return;
|
|
198
|
+
const held = kept.get(frame.carry);
|
|
199
|
+
if (held === undefined && frame.index !== 0) {
|
|
200
|
+
interrupt(frame.authority);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (held !== undefined &&
|
|
204
|
+
(held.fenced !== null || frame.index > held.parts || frame.index < (held.resume?.parts ?? 0))) {
|
|
205
|
+
await lost(frame.authority, frame.carry);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (held === undefined) {
|
|
209
|
+
await forget([...kept.values()]
|
|
210
|
+
.filter((other) => other.authority === frame.authority && other.fenced === null)
|
|
211
|
+
.map((other) => other.carry));
|
|
212
|
+
}
|
|
213
|
+
const start = held ?? {
|
|
214
|
+
authority: frame.authority,
|
|
215
|
+
carry: frame.carry,
|
|
216
|
+
logEpoch: lines.get(frame.authority)?.logEpoch ?? null,
|
|
217
|
+
after: frame.after,
|
|
218
|
+
parts: 0,
|
|
219
|
+
resume: null,
|
|
220
|
+
fenced: null,
|
|
221
|
+
};
|
|
222
|
+
const next = { ...start, parts: frame.index + 1, resume: resumed(start, frame) };
|
|
223
|
+
const part = { entries: frame.entries };
|
|
224
|
+
try {
|
|
225
|
+
await port.keep(frame.carry, next, { index: frame.index, value: part });
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
await lost(frame.authority, frame.carry);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
kept.set(frame.carry, next);
|
|
232
|
+
via?.received(frame.authority, frame.carry, frame.index);
|
|
233
|
+
}
|
|
234
|
+
async function keptEntries(carry, parts) {
|
|
235
|
+
if (parts === 0)
|
|
236
|
+
return [];
|
|
237
|
+
if (receipts === null)
|
|
238
|
+
return null;
|
|
239
|
+
const read = (await receipts.parts(carry, parts));
|
|
240
|
+
if (read.length !== parts)
|
|
241
|
+
return null;
|
|
242
|
+
const entries = assembled(read.flatMap((part) => part.entries));
|
|
243
|
+
return entries !== null && (await verified(entries)) ? entries : null;
|
|
244
|
+
}
|
|
245
|
+
async function carried(frame) {
|
|
246
|
+
await loaded;
|
|
247
|
+
const held = kept.get(frame.carry);
|
|
248
|
+
if (frame.parts > 0 && held?.parts !== frame.parts) {
|
|
249
|
+
await lost(frame.authority, frame.carry);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const entries = await keptEntries(frame.carry, frame.parts).catch(() => null);
|
|
253
|
+
if (entries === null) {
|
|
254
|
+
await lost(frame.authority, frame.carry);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const fenced = {
|
|
258
|
+
...(held ?? {
|
|
259
|
+
authority: frame.authority,
|
|
260
|
+
carry: frame.carry,
|
|
261
|
+
logEpoch: frame.log_epoch,
|
|
262
|
+
after: frame.after,
|
|
263
|
+
parts: 0,
|
|
264
|
+
resume: null,
|
|
265
|
+
}),
|
|
266
|
+
after: frame.after,
|
|
267
|
+
fenced: { position: frame.position, logEpoch: frame.log_epoch },
|
|
268
|
+
};
|
|
269
|
+
try {
|
|
270
|
+
await receipts?.keep(frame.carry, fenced);
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
await lost(frame.authority, frame.carry);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
kept.set(frame.carry, fenced);
|
|
277
|
+
publish(frame, entries);
|
|
278
|
+
}
|
|
279
|
+
async function load(port) {
|
|
280
|
+
try {
|
|
281
|
+
for (const { key, head } of await port.heads())
|
|
282
|
+
kept.set(key, head);
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
kept.clear();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async function restored(room) {
|
|
289
|
+
const line = lines.get(room.authority);
|
|
290
|
+
let at = room.position;
|
|
291
|
+
const chain = [];
|
|
292
|
+
for (;;) {
|
|
293
|
+
const next = [...kept.values()].find((carry) => carry.authority === room.authority &&
|
|
294
|
+
carry.after === at &&
|
|
295
|
+
carry.fenced?.logEpoch === room.log_epoch);
|
|
296
|
+
if (next?.fenced == null)
|
|
297
|
+
break;
|
|
298
|
+
const entries = await keptEntries(next.carry, next.parts).catch(() => null);
|
|
299
|
+
if (entries === null)
|
|
300
|
+
break;
|
|
301
|
+
chain.push({ carry: next, entries });
|
|
302
|
+
at = next.fenced.position;
|
|
303
|
+
}
|
|
304
|
+
const partial = [...kept.values()].find((carry) => carry.authority === room.authority &&
|
|
305
|
+
carry.fenced === null &&
|
|
306
|
+
carry.logEpoch === room.log_epoch &&
|
|
307
|
+
carry.after === at &&
|
|
308
|
+
carry.resume !== null &&
|
|
309
|
+
carry.resume.parts > 0);
|
|
310
|
+
const stale = [...kept.values()].filter((carry) => carry.authority === room.authority &&
|
|
311
|
+
carry !== partial &&
|
|
312
|
+
!chain.some((link) => link.carry === carry));
|
|
313
|
+
await forget(stale.map((carry) => carry.carry));
|
|
314
|
+
if (line !== undefined && line.logEpoch === room.log_epoch && chain.length > 0) {
|
|
315
|
+
const frames = chain.map(({ carry, entries }) => ({
|
|
316
|
+
after: BigInt(carry.after),
|
|
317
|
+
position: BigInt(carry.fenced.position),
|
|
318
|
+
entries,
|
|
319
|
+
}));
|
|
320
|
+
lines.set(room.authority, { ...line, frames: [...line.frames, ...frames] });
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
...room,
|
|
324
|
+
position: at,
|
|
325
|
+
...(partial?.resume == null
|
|
326
|
+
? {}
|
|
327
|
+
: {
|
|
328
|
+
carry: {
|
|
329
|
+
id: partial.carry,
|
|
330
|
+
parts: partial.resume.parts,
|
|
331
|
+
from: partial.resume.from,
|
|
332
|
+
target: partial.resume.target,
|
|
333
|
+
handled: partial.resume.handled,
|
|
334
|
+
...(partial.resume.piece === null ? {} : { piece: partial.resume.piece }),
|
|
335
|
+
},
|
|
336
|
+
}),
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function handle(frame, via) {
|
|
114
340
|
if (frame.type === 'signal') {
|
|
115
341
|
const mode = frame.service_mode;
|
|
116
342
|
if (mode !== 'open' && mode !== 'write-halted')
|
|
@@ -147,8 +373,12 @@ export function houseNotifications(transport, mode) {
|
|
|
147
373
|
converse(frame);
|
|
148
374
|
return;
|
|
149
375
|
}
|
|
150
|
-
if (frame.type
|
|
151
|
-
return;
|
|
376
|
+
if (frame.type === 'part')
|
|
377
|
+
return stage(frame, via);
|
|
378
|
+
if (frame.type === 'carried')
|
|
379
|
+
return carried(frame);
|
|
380
|
+
}
|
|
381
|
+
function publish(frame, entries) {
|
|
152
382
|
const at = position(frame.position);
|
|
153
383
|
const after = position(frame.after);
|
|
154
384
|
if (at === null || after === null || after >= at) {
|
|
@@ -162,7 +392,7 @@ export function houseNotifications(transport, mode) {
|
|
|
162
392
|
const last = line.frames.at(-1);
|
|
163
393
|
if (last !== undefined && at <= last.position)
|
|
164
394
|
return;
|
|
165
|
-
const held = { after, position: at, entries
|
|
395
|
+
const held = { after, position: at, entries };
|
|
166
396
|
const next = last !== undefined && after <= last.position ? [...line.frames, held] : [held];
|
|
167
397
|
lines.set(frame.authority, { ...line, frames: next.length > HELD_FRAMES_MAX ? [] : next });
|
|
168
398
|
}
|
|
@@ -171,10 +401,29 @@ export function houseNotifications(transport, mode) {
|
|
|
171
401
|
}
|
|
172
402
|
announce(frame.authority, { position: frame.position, logEpoch: frame.log_epoch });
|
|
173
403
|
}
|
|
174
|
-
stream.observe(
|
|
404
|
+
stream.observe((frame) => {
|
|
405
|
+
const via = acknowledging;
|
|
406
|
+
if (verifying === 0) {
|
|
407
|
+
const handled = handle(frame, via);
|
|
408
|
+
if (handled === undefined)
|
|
409
|
+
return;
|
|
410
|
+
verifying += 1;
|
|
411
|
+
receiving = handled.finally(() => {
|
|
412
|
+
verifying -= 1;
|
|
413
|
+
});
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
verifying += 1;
|
|
417
|
+
receiving = receiving
|
|
418
|
+
.then(() => handle(frame, via))
|
|
419
|
+
.finally(() => {
|
|
420
|
+
verifying -= 1;
|
|
421
|
+
});
|
|
422
|
+
});
|
|
175
423
|
stream.opened(reopened);
|
|
176
424
|
stream.closed((revoked) => {
|
|
177
425
|
opening = null;
|
|
426
|
+
acknowledging = null;
|
|
178
427
|
emit({ kind: 'closed', revoked });
|
|
179
428
|
});
|
|
180
429
|
return {
|
|
@@ -198,8 +447,12 @@ export function houseNotifications(transport, mode) {
|
|
|
198
447
|
};
|
|
199
448
|
},
|
|
200
449
|
},
|
|
201
|
-
observe(notify) {
|
|
450
|
+
observe(notify, port) {
|
|
202
451
|
observers.add(notify);
|
|
452
|
+
if (receipts === null) {
|
|
453
|
+
receipts = port;
|
|
454
|
+
loaded = load(port);
|
|
455
|
+
}
|
|
203
456
|
if (opening !== null) {
|
|
204
457
|
queueMicrotask(() => {
|
|
205
458
|
if (observers.has(notify))
|
|
@@ -220,7 +473,30 @@ export function houseNotifications(transport, mode) {
|
|
|
220
473
|
}
|
|
221
474
|
const current = opening;
|
|
222
475
|
opening = null;
|
|
223
|
-
current
|
|
476
|
+
if (current === null)
|
|
477
|
+
return;
|
|
478
|
+
void loaded
|
|
479
|
+
.then(() => Promise.all(rooms.map(restored)))
|
|
480
|
+
.then((presented) => {
|
|
481
|
+
current.hold(presented, runtime);
|
|
482
|
+
for (const [index, room] of presented.entries()) {
|
|
483
|
+
if (room.position === rooms[index].position)
|
|
484
|
+
continue;
|
|
485
|
+
announce(room.authority, { position: room.position, logEpoch: room.log_epoch });
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
},
|
|
489
|
+
published(locator, at) {
|
|
490
|
+
const authority = [...locators].find(([, held]) => held.includes(locator))?.[0];
|
|
491
|
+
const reached = position(at.position);
|
|
492
|
+
if (authority === undefined || reached === null)
|
|
493
|
+
return;
|
|
494
|
+
void forget([...kept.values()]
|
|
495
|
+
.filter((carry) => carry.authority === authority &&
|
|
496
|
+
carry.fenced !== null &&
|
|
497
|
+
carry.fenced.logEpoch === at.logEpoch &&
|
|
498
|
+
BigInt(carry.fenced.position) <= reached)
|
|
499
|
+
.map((carry) => carry.carry));
|
|
224
500
|
},
|
|
225
501
|
reopen() {
|
|
226
502
|
const current = opening;
|
|
@@ -239,6 +515,9 @@ export function houseNotifications(transport, mode) {
|
|
|
239
515
|
if (!locators.has(authority))
|
|
240
516
|
lines.delete(authority);
|
|
241
517
|
}
|
|
518
|
+
void loaded.then(() => forget([...kept.values()]
|
|
519
|
+
.filter((carry) => !locators.has(carry.authority))
|
|
520
|
+
.map((carry) => carry.carry)));
|
|
242
521
|
},
|
|
243
522
|
reach(authority, at) {
|
|
244
523
|
const line = lines.get(authority);
|
package/dist/transport.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export type HouseByteTransfer = {
|
|
|
31
31
|
};
|
|
32
32
|
export type HouseStreamEntry = {
|
|
33
33
|
path: string;
|
|
34
|
-
kind: 'edited' | 'removed';
|
|
34
|
+
kind: 'edited' | 'removed' | 'piece';
|
|
35
35
|
revision?: string;
|
|
36
36
|
sha256?: string;
|
|
37
37
|
text?: string;
|
|
@@ -46,6 +46,7 @@ export type HouseStreamEntry = {
|
|
|
46
46
|
name: string;
|
|
47
47
|
};
|
|
48
48
|
created_at?: string;
|
|
49
|
+
continued?: true;
|
|
49
50
|
};
|
|
50
51
|
type LiveFact<K extends ConversationEvent['kind']> = Extract<ConversationEvent, {
|
|
51
52
|
kind: K;
|
|
@@ -59,12 +60,23 @@ export type HouseListedConversation = {
|
|
|
59
60
|
outcome: string | null;
|
|
60
61
|
};
|
|
61
62
|
export type HouseStreamFrame = {
|
|
62
|
-
type: '
|
|
63
|
+
type: 'part';
|
|
63
64
|
authority: string;
|
|
65
|
+
carry: string;
|
|
66
|
+
index: number;
|
|
67
|
+
after: string;
|
|
68
|
+
from: string;
|
|
69
|
+
target: string;
|
|
70
|
+
handled: number;
|
|
71
|
+
entries: readonly HouseStreamEntry[];
|
|
72
|
+
} | {
|
|
73
|
+
type: 'carried';
|
|
74
|
+
authority: string;
|
|
75
|
+
carry: string;
|
|
76
|
+
parts: number;
|
|
64
77
|
after: string;
|
|
65
78
|
position: string;
|
|
66
79
|
log_epoch: string;
|
|
67
|
-
entries: readonly HouseStreamEntry[];
|
|
68
80
|
} | {
|
|
69
81
|
type: 'opening';
|
|
70
82
|
expired: readonly string[];
|
|
@@ -105,13 +117,26 @@ export type HouseStreamFrame = {
|
|
|
105
117
|
type: 'signal';
|
|
106
118
|
service_mode: string;
|
|
107
119
|
};
|
|
120
|
+
export type HouseHeldCarry = {
|
|
121
|
+
id: string;
|
|
122
|
+
parts: number;
|
|
123
|
+
from: string;
|
|
124
|
+
target: string;
|
|
125
|
+
handled: number;
|
|
126
|
+
piece?: {
|
|
127
|
+
sha256: string;
|
|
128
|
+
offset: number;
|
|
129
|
+
};
|
|
130
|
+
};
|
|
108
131
|
export type HouseHeldRoom = {
|
|
109
132
|
authority: string;
|
|
110
133
|
position: string;
|
|
111
134
|
log_epoch: string;
|
|
135
|
+
carry?: HouseHeldCarry;
|
|
112
136
|
};
|
|
113
137
|
export type HouseOpening = {
|
|
114
138
|
hold(rooms: readonly HouseHeldRoom[], runtime: string): void;
|
|
139
|
+
received(authority: string, carry: string, index: number): void;
|
|
115
140
|
close(): void;
|
|
116
141
|
};
|
|
117
142
|
export type HouseStream = {
|