@agentshouse/mdruntime 0.1.0-ca7c26c6bc27e → 0.1.0-ca92b54804c33
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 +25 -12
- package/dist/adapter.d.ts +199 -54
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +1 -9
- package/dist/attached-model.d.ts +7 -10
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +40 -28
- package/dist/attempt.d.ts +16 -0
- package/dist/attempt.d.ts.map +1 -0
- package/dist/attempt.js +70 -0
- package/dist/binary.d.ts +18 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +37 -5
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +336 -51
- package/dist/chain.d.ts +12 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +64 -0
- package/dist/change-set.d.ts +9 -14
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +11 -56
- package/dist/change.d.ts +46 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +34 -0
- package/dist/client.d.ts +44 -18
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +161 -30
- package/dist/collection.d.ts +13 -14
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +52 -56
- package/dist/compile.d.ts +11 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +586 -0
- package/dist/connection.d.ts +67 -24
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +4414 -1205
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +7 -0
- package/dist/conversation.d.ts +65 -21
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +922 -273
- package/dist/digest.d.ts +7 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +113 -0
- package/dist/durability.d.ts +10 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +5 -0
- package/dist/entry.d.ts +20 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +161 -0
- package/dist/expectation.d.ts +21 -0
- package/dist/expectation.d.ts.map +1 -0
- package/dist/expectation.js +70 -0
- package/dist/failure.d.ts +13 -0
- package/dist/failure.d.ts.map +1 -0
- package/dist/failure.js +19 -0
- package/dist/file.d.ts +2 -19
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +7 -122
- package/dist/host.d.ts +11 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +134 -34
- package/dist/identity.d.ts +3 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +13 -1
- package/dist/index.d.ts +24 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/job.d.ts +14 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +28 -0
- package/dist/live.d.ts +23 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +165 -0
- package/dist/media.d.ts +8 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +218 -0
- package/dist/memory.d.ts +22 -8
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +299 -94
- package/dist/messages.d.ts +28 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +91 -0
- package/dist/model.d.ts +17 -12
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +9 -9
- package/dist/name.d.ts +4 -0
- package/dist/name.d.ts.map +1 -0
- package/dist/name.js +122 -0
- package/dist/observation.d.ts +22 -4
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +120 -14
- package/dist/open.d.ts +2 -0
- package/dist/open.d.ts.map +1 -0
- package/dist/open.js +1 -0
- package/dist/opened.d.ts +22 -0
- package/dist/opened.d.ts.map +1 -0
- package/dist/opened.js +107 -0
- package/dist/outcome.d.ts +72 -5
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +81 -1
- package/dist/overlay.d.ts +16 -5
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +147 -180
- package/dist/paths.d.ts +1 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +4 -16
- package/dist/query.d.ts +42 -181
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -423
- package/dist/reach.d.ts +19 -5
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +298 -92
- package/dist/react.d.ts +45 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +54 -0
- package/dist/readiness.d.ts +50 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +27 -0
- package/dist/recovery.d.ts +26 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +232 -0
- package/dist/refresh.d.ts +20 -7
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +155 -59
- package/dist/replica.d.ts +61 -34
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +355 -162
- package/dist/room-draft.d.ts +59 -11
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +276 -80
- package/dist/rows.d.ts +32 -34
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +65 -87
- package/dist/save.d.ts +74 -6
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +902 -182
- package/dist/search.d.ts +15 -54
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +10 -121
- package/dist/seed.d.ts +16 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +75 -0
- package/dist/shared.d.ts +15 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +421 -0
- package/dist/status.d.ts +7 -4
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -15
- package/dist/store.d.ts +174 -68
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +367 -360
- package/dist/stored.d.ts +156 -0
- package/dist/stored.d.ts.map +1 -0
- package/dist/stored.js +635 -0
- package/dist/tables.d.ts +381 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +700 -0
- package/dist/transfer.d.ts +0 -5
- package/dist/transfer.d.ts.map +1 -1
- package/dist/turn.d.ts +9 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +75 -0
- package/dist/validation.d.ts +29 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +104 -0
- package/dist/windows.d.ts +11 -0
- package/dist/windows.d.ts.map +1 -0
- package/dist/windows.js +16 -0
- package/dist/wire.d.ts +179 -45
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +72 -46
- package/package.json +33 -10
- package/AGENTS.md +0 -215
- package/dist/attachment.d.ts +0 -3
- package/dist/attachment.d.ts.map +0 -1
- package/dist/attachment.js +0 -8
- package/dist/read-side.d.ts +0 -18
- package/dist/read-side.d.ts.map +0 -1
- package/dist/read-side.js +0 -377
package/dist/reach.js
CHANGED
|
@@ -1,30 +1,38 @@
|
|
|
1
1
|
import { unheldView } from './conversation.js';
|
|
2
2
|
import { modelTypes, NO_MODEL } from './model.js';
|
|
3
|
-
import {
|
|
3
|
+
import { changeOf, digestApplied, digestOf, observing, } from './observation.js';
|
|
4
|
+
import { runtimeError } from './failure.js';
|
|
5
|
+
import { failure, lockedRoom, ROOM_LOCKED, undeclared, UNREADABLE, } from './outcome.js';
|
|
4
6
|
import { beneath } from './paths.js';
|
|
5
|
-
import {
|
|
6
|
-
import { moveTarget, readParts } from './room-draft.js';
|
|
7
|
+
import { moveTarget, readParts, staleWrite, } from './room-draft.js';
|
|
7
8
|
import { documentKey } from './rows.js';
|
|
8
9
|
const UNHELD_TYPES = { collection: modelTypes(NO_MODEL), typedSelection: null };
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return Promise.resolve(UNDECLARED);
|
|
10
|
+
function unconversed(name) {
|
|
11
|
+
return () => Promise.resolve(undeclared(name));
|
|
12
12
|
}
|
|
13
13
|
const NO_CONVERSATION = {
|
|
14
14
|
list: () => Promise.resolve([]),
|
|
15
15
|
read: (conversation) => Promise.resolve(unheldView(conversation)),
|
|
16
|
-
create:
|
|
17
|
-
send:
|
|
18
|
-
answer:
|
|
19
|
-
interrupt:
|
|
20
|
-
kill:
|
|
21
|
-
|
|
22
|
-
delete: undeclared,
|
|
16
|
+
create: unconversed('conversations/create'),
|
|
17
|
+
send: unconversed('conversations/send'),
|
|
18
|
+
answer: unconversed('conversations/interaction/answer'),
|
|
19
|
+
interrupt: unconversed('conversations/interrupt'),
|
|
20
|
+
kill: unconversed('conversations/kill'),
|
|
21
|
+
delete: unconversed('conversations/delete'),
|
|
23
22
|
};
|
|
24
23
|
const NO_CHAT_DRAFT = {
|
|
25
24
|
write: () => Promise.resolve(),
|
|
25
|
+
prepend: () => Promise.resolve(),
|
|
26
26
|
discard: () => Promise.resolve(),
|
|
27
27
|
};
|
|
28
|
+
function areasOf(reach) {
|
|
29
|
+
return reach.areas ?? [];
|
|
30
|
+
}
|
|
31
|
+
function fenced(reach, room, path) {
|
|
32
|
+
return areasOf(reach).some((area) => area.room === room &&
|
|
33
|
+
beneath(area.path, path) &&
|
|
34
|
+
!area.open.some((open) => beneath(open, path)));
|
|
35
|
+
}
|
|
28
36
|
export function holds(reach, room) {
|
|
29
37
|
return (reach === null ||
|
|
30
38
|
reach.rooms.includes(room) ||
|
|
@@ -45,8 +53,9 @@ export function governs(reach, room) {
|
|
|
45
53
|
}
|
|
46
54
|
function covers(reach, room, path) {
|
|
47
55
|
return (reach === null ||
|
|
48
|
-
reach
|
|
49
|
-
|
|
56
|
+
(!fenced(reach, room, path) &&
|
|
57
|
+
(reach.rooms.includes(room) ||
|
|
58
|
+
reach.documents.some((document) => document.room === room && document.path === path))));
|
|
50
59
|
}
|
|
51
60
|
function conversing(reach) {
|
|
52
61
|
return reach === null || reach.conversations;
|
|
@@ -59,25 +68,13 @@ export function planReach(reach) {
|
|
|
59
68
|
documents: [
|
|
60
69
|
...new Set(reach.documents.map(({ room, path }) => documentKey(room, path))),
|
|
61
70
|
].sort(),
|
|
71
|
+
areas: areasOf(reach)
|
|
72
|
+
.map(({ room, path, open }) => ({ room, path, open: [...new Set(open)].sort() }))
|
|
73
|
+
.sort((left, right) => documentKey(left.room, left.path) < documentKey(right.room, right.path) ? -1 : 1),
|
|
62
74
|
};
|
|
63
75
|
}
|
|
64
76
|
function roomsWithin(reach, rooms) {
|
|
65
|
-
|
|
66
|
-
return rooms;
|
|
67
|
-
return rooms
|
|
68
|
-
.filter((room) => holds(reach, room.locator))
|
|
69
|
-
.map((room) => {
|
|
70
|
-
if (wholly(reach, room.locator))
|
|
71
|
-
return room;
|
|
72
|
-
const governed = governs(reach, room);
|
|
73
|
-
return {
|
|
74
|
-
...room,
|
|
75
|
-
model: governed.collection ? room.model : modelTypes(NO_MODEL),
|
|
76
|
-
typedSelection: governed.selection ? room.typedSelection : null,
|
|
77
|
-
roomVersion: null,
|
|
78
|
-
attachments: [],
|
|
79
|
-
};
|
|
80
|
-
});
|
|
77
|
+
return rooms.filter((room) => wholly(reach, room.locator));
|
|
81
78
|
}
|
|
82
79
|
function typesWithin(reach, room, types) {
|
|
83
80
|
if (room === undefined)
|
|
@@ -89,7 +86,7 @@ function typesWithin(reach, room, types) {
|
|
|
89
86
|
};
|
|
90
87
|
}
|
|
91
88
|
function moves(reach, room, move) {
|
|
92
|
-
return move === undefined || covers(reach, room, moveTarget(move));
|
|
89
|
+
return move === undefined || covers(reach, move.room ?? room, moveTarget(move));
|
|
93
90
|
}
|
|
94
91
|
function draftWithin(reach, draft) {
|
|
95
92
|
return covers(reach, draft.room, draft.path) && moves(reach, draft.room, draft.parts.move);
|
|
@@ -103,7 +100,7 @@ function shownDraft(reach, draft) {
|
|
|
103
100
|
function writeWithin(reach, write) {
|
|
104
101
|
return write.outcome === 'written'
|
|
105
102
|
? { outcome: 'written', roomDraft: shownDraft(reach, write.roomDraft) }
|
|
106
|
-
:
|
|
103
|
+
: staleWrite(shownDraft(reach, write.current));
|
|
107
104
|
}
|
|
108
105
|
function savedWithin(reach, room, result) {
|
|
109
106
|
if (reach === null || result.outcome === 'saved')
|
|
@@ -123,128 +120,337 @@ function statusWithin(reach, status) {
|
|
|
123
120
|
return {
|
|
124
121
|
...status,
|
|
125
122
|
attentionRequired: status.attentionRequired.filter((entry) => covers(reach, entry.room, entry.path)),
|
|
123
|
+
unsent: status.unsent.filter((change) => covers(reach, change.room, change.path)),
|
|
126
124
|
};
|
|
127
125
|
}
|
|
128
126
|
function queueWithin(reach, room, entries) {
|
|
129
|
-
return entries
|
|
130
|
-
(
|
|
127
|
+
return entries
|
|
128
|
+
.filter(({ batch }) => batch.changes.every((change) => covers(reach, room, change.path) &&
|
|
129
|
+
(change.kind !== 'move' || covers(reach, room, change.to))))
|
|
130
|
+
.map((entry) => {
|
|
131
|
+
const { between, ...batch } = entry.batch;
|
|
132
|
+
if (between === undefined)
|
|
133
|
+
return entry;
|
|
134
|
+
return wholly(reach, between.source.room) && wholly(reach, between.destination.room)
|
|
135
|
+
? entry
|
|
136
|
+
: { ...entry, batch };
|
|
137
|
+
});
|
|
131
138
|
}
|
|
132
139
|
function unreached(room) {
|
|
133
|
-
return
|
|
140
|
+
return runtimeError('unknown-room', `Unknown Room ${room}`);
|
|
141
|
+
}
|
|
142
|
+
function lockedError(room) {
|
|
143
|
+
return runtimeError(ROOM_LOCKED, `${room} is locked until House confirms its access`, true);
|
|
144
|
+
}
|
|
145
|
+
function deviceUnreached() {
|
|
146
|
+
return runtimeError('device-unreached', 'Only the panel reaches this device');
|
|
134
147
|
}
|
|
135
|
-
|
|
148
|
+
const ATTACH_REQUIRED = 'attach-required';
|
|
149
|
+
function attachRequired() {
|
|
150
|
+
return runtimeError(ATTACH_REQUIRED, 'App code takes a file in with binaryFile.attach');
|
|
151
|
+
}
|
|
152
|
+
function carriesFile(parts) {
|
|
153
|
+
return parts !== null && typeof parts === 'object' && Object.hasOwn(parts, 'file');
|
|
154
|
+
}
|
|
155
|
+
export function narrowed(runtime, given, port) {
|
|
136
156
|
const { md, watch } = runtime;
|
|
137
157
|
let reach = given;
|
|
138
158
|
const open = new Set();
|
|
139
|
-
function following(begin) {
|
|
159
|
+
function following(begin, locking = false) {
|
|
140
160
|
let stop = begin();
|
|
141
161
|
const restart = () => {
|
|
142
162
|
stop();
|
|
143
163
|
stop = begin();
|
|
144
164
|
};
|
|
145
165
|
open.add(restart);
|
|
166
|
+
const unwatch = locking ? runtime.watchLocks(restart) : () => { };
|
|
146
167
|
return () => {
|
|
147
168
|
open.delete(restart);
|
|
169
|
+
unwatch();
|
|
170
|
+
stop();
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const unlocking = (rooms, locked) => {
|
|
174
|
+
const held = (rooms ?? []).filter((room) => locked.includes(room));
|
|
175
|
+
return held.length === 0 ? null : { pending: held.map((room) => ({ room, awaits: 'unlock' })) };
|
|
176
|
+
};
|
|
177
|
+
const unlocked = (entries, locked) => entries.filter((entry) => !locked.includes(entry.room));
|
|
178
|
+
const intoLocked = async (held) => {
|
|
179
|
+
for (const move of held) {
|
|
180
|
+
if (move?.room !== undefined && (await runtime.locked(move.room)))
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
};
|
|
185
|
+
function ordered(deliver) {
|
|
186
|
+
let open = true;
|
|
187
|
+
let shown = Promise.resolve();
|
|
188
|
+
return {
|
|
189
|
+
push(making) {
|
|
190
|
+
shown = shown.then(() => making.then((value) => {
|
|
191
|
+
if (open)
|
|
192
|
+
deliver(value);
|
|
193
|
+
}, () => { }), () => { });
|
|
194
|
+
},
|
|
195
|
+
stop() {
|
|
196
|
+
open = false;
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function unlockedWatch(topic, input, deliver, fail, within) {
|
|
201
|
+
const shown = ordered(deliver);
|
|
202
|
+
const stop = watch(topic, input, (message) => {
|
|
203
|
+
shown.push(runtime.lockedRooms().then((locked) => within(message, locked)));
|
|
204
|
+
}, fail);
|
|
205
|
+
return () => {
|
|
206
|
+
shown.stop();
|
|
207
|
+
stop();
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function lockedFirst(room, start, shut) {
|
|
211
|
+
let open = true;
|
|
212
|
+
let stop = () => { };
|
|
213
|
+
void (async () => {
|
|
214
|
+
const closed = !holds(reach, room) || (await runtime.locked(room));
|
|
215
|
+
if (!open)
|
|
216
|
+
return;
|
|
217
|
+
if (closed)
|
|
218
|
+
shut();
|
|
219
|
+
else
|
|
220
|
+
stop = start();
|
|
221
|
+
})();
|
|
222
|
+
return () => {
|
|
223
|
+
open = false;
|
|
148
224
|
stop();
|
|
149
225
|
};
|
|
150
226
|
}
|
|
151
|
-
const within = (topic, input, deliver) => {
|
|
227
|
+
const within = (topic, input, deliver, fail) => {
|
|
152
228
|
if (topic === 'rooms') {
|
|
153
|
-
return following(() => watch(topic, input, (rooms) => deliver(roomsWithin(reach, rooms))));
|
|
229
|
+
return following(() => watch(topic, input, (rooms) => deliver(roomsWithin(reach, rooms)), fail));
|
|
230
|
+
}
|
|
231
|
+
if (topic === 'readiness') {
|
|
232
|
+
return following(() => runtime.watchReadiness(port, reach, deliver));
|
|
154
233
|
}
|
|
155
234
|
if (topic === 'sync-status') {
|
|
156
|
-
return following(() =>
|
|
235
|
+
return following(() => unlockedWatch(topic, input, deliver, fail, (status, locked) => {
|
|
236
|
+
const shown = statusWithin(reach, status);
|
|
237
|
+
return { ...shown, attentionRequired: unlocked(shown.attentionRequired, locked) };
|
|
238
|
+
}), true);
|
|
157
239
|
}
|
|
158
240
|
if (topic === 'room-drafts') {
|
|
159
|
-
return following(() =>
|
|
241
|
+
return following(() => unlockedWatch(topic, input, deliver, fail, (drafts, locked) => unlocked(draftsWithin(reach, drafts), locked)), true);
|
|
160
242
|
}
|
|
161
243
|
if (topic === 'queue') {
|
|
162
244
|
const room = input[0];
|
|
163
|
-
return following(() =>
|
|
164
|
-
if (!holds(reach, room)) {
|
|
165
|
-
deliver([]);
|
|
166
|
-
return () => { };
|
|
167
|
-
}
|
|
168
|
-
return watch(topic, input, (entries) => deliver(queueWithin(reach, room, entries)));
|
|
169
|
-
});
|
|
245
|
+
return following(() => lockedFirst(room, () => watch(topic, input, (entries) => deliver(queueWithin(reach, room, entries)), fail), () => deliver([])), true);
|
|
170
246
|
}
|
|
171
247
|
if (topic === 'query') {
|
|
172
|
-
const
|
|
248
|
+
const planning = runtime.plan(input[0], reach);
|
|
173
249
|
let held = null;
|
|
174
250
|
return following(() => {
|
|
175
|
-
let
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
251
|
+
let open = true;
|
|
252
|
+
let stop = () => { };
|
|
253
|
+
void (async () => {
|
|
254
|
+
const plan = await planning;
|
|
255
|
+
const locked = await runtime.lockedRooms();
|
|
256
|
+
if (!open)
|
|
180
257
|
return;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
held
|
|
185
|
-
|
|
258
|
+
const pending = unlocking(plan.rooms, locked);
|
|
259
|
+
if (pending !== null) {
|
|
260
|
+
const answer = { result: pending, keys: [] };
|
|
261
|
+
if (held === null || changeOf(held, answer) !== null)
|
|
262
|
+
deliver(answer);
|
|
263
|
+
held = digestOf(answer);
|
|
186
264
|
return;
|
|
187
265
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
266
|
+
let first = true;
|
|
267
|
+
const heard = (message) => {
|
|
268
|
+
if (!first) {
|
|
269
|
+
held = digestApplied(held, message);
|
|
270
|
+
deliver(message);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
first = false;
|
|
274
|
+
const change = held === null ? message : changeOf(held, message);
|
|
275
|
+
held = digestOf(message);
|
|
276
|
+
if (change !== null)
|
|
277
|
+
deliver(change);
|
|
278
|
+
};
|
|
279
|
+
const current = { ...plan, reach: planReach(reach), locked };
|
|
280
|
+
stop = runtime.watchPlan(current, (message) => {
|
|
281
|
+
if (open)
|
|
282
|
+
heard(message);
|
|
283
|
+
}, (error) => {
|
|
284
|
+
if (open)
|
|
285
|
+
fail(error);
|
|
286
|
+
});
|
|
287
|
+
})().catch((error) => {
|
|
288
|
+
if (open)
|
|
289
|
+
fail(error);
|
|
193
290
|
});
|
|
194
|
-
|
|
291
|
+
return () => {
|
|
292
|
+
open = false;
|
|
293
|
+
stop();
|
|
294
|
+
};
|
|
295
|
+
}, true);
|
|
195
296
|
}
|
|
196
297
|
if (topic === 'conversation') {
|
|
197
298
|
return following(() => {
|
|
198
299
|
if (conversing(reach))
|
|
199
|
-
return watch(topic, input, deliver);
|
|
300
|
+
return watch(topic, input, deliver, fail);
|
|
200
301
|
deliver(unheldView(input[0]));
|
|
201
302
|
return () => { };
|
|
202
303
|
});
|
|
203
304
|
}
|
|
204
|
-
|
|
305
|
+
if (topic === 'conversations' || topic === 'agents') {
|
|
306
|
+
return following(() => {
|
|
307
|
+
if (conversing(reach))
|
|
308
|
+
return watch(topic, input, deliver, fail);
|
|
309
|
+
deliver([]);
|
|
310
|
+
return () => { };
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
return watch(topic, input, deliver, fail);
|
|
205
314
|
};
|
|
315
|
+
const files = new Map();
|
|
316
|
+
let handed = 0;
|
|
317
|
+
async function hand(reading) {
|
|
318
|
+
const answer = await reading;
|
|
319
|
+
if (answer.kind !== 'binary')
|
|
320
|
+
return answer;
|
|
321
|
+
handed += 1;
|
|
322
|
+
const file = String(handed);
|
|
323
|
+
files.set(file, answer.file);
|
|
324
|
+
return { ...answer, file };
|
|
325
|
+
}
|
|
206
326
|
const conversation = () => (conversing(reach) ? md.conversation : NO_CONVERSATION);
|
|
207
327
|
const chatDraft = () => (conversing(reach) ? md.chatDraft : NO_CHAT_DRAFT);
|
|
208
328
|
const view = {
|
|
209
329
|
...md,
|
|
210
|
-
rooms: () => roomsWithin(reach,
|
|
211
|
-
query: ((input) =>
|
|
330
|
+
rooms: () => md.rooms().then((rooms) => roomsWithin(reach, rooms)),
|
|
331
|
+
query: (async (input) => {
|
|
332
|
+
const plan = await runtime.plan(input, reach);
|
|
333
|
+
const locked = await runtime.lockedRooms();
|
|
334
|
+
return unlocking(plan.rooms, locked) ?? runtime.query({ ...plan, locked });
|
|
335
|
+
}),
|
|
212
336
|
search: (input) => runtime.search(input, reach),
|
|
213
|
-
types(room) {
|
|
337
|
+
async types(room) {
|
|
338
|
+
if (holds(reach, room) && (await runtime.locked(room)))
|
|
339
|
+
throw lockedError(room);
|
|
214
340
|
if (wholly(reach, room))
|
|
215
341
|
return md.types(room);
|
|
216
342
|
if (!holds(reach, room))
|
|
217
343
|
return structuredClone(UNHELD_TYPES);
|
|
218
|
-
|
|
219
|
-
return typesWithin(reach, listed, md.types(room));
|
|
344
|
+
return typesWithin(reach, await runtime.room(room), await md.types(room));
|
|
220
345
|
},
|
|
221
346
|
async save(room, roomDrafts, writtenBy) {
|
|
222
347
|
if (!holds(reach, room))
|
|
223
348
|
throw unreached(room);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
349
|
+
if (await runtime.locked(room)) {
|
|
350
|
+
return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
|
|
351
|
+
}
|
|
352
|
+
const held = (await Promise.all(roomDrafts.map(({ path }) => md.roomDraft.read(room, path)))).map((draft) => draft?.parts.move);
|
|
353
|
+
const outside = roomDrafts.find(({ path }, at) => !covers(reach, room, path) || !moves(reach, room, held[at]));
|
|
354
|
+
if (outside !== undefined) {
|
|
355
|
+
return { outcome: 'refused', code: 'stale', retryable: false, file: outside.path };
|
|
356
|
+
}
|
|
357
|
+
if (await intoLocked(held))
|
|
358
|
+
return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
|
|
228
359
|
return savedWithin(reach, room, await md.save(room, roomDrafts, writtenBy));
|
|
229
360
|
},
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
361
|
+
async saveParts(room, parts, writtenBy) {
|
|
362
|
+
if (!holds(reach, room))
|
|
363
|
+
throw unreached(room);
|
|
364
|
+
const filed = reach === null ? undefined : parts.find(({ parts: one }) => carriesFile(one));
|
|
365
|
+
if (filed !== undefined) {
|
|
366
|
+
return { outcome: 'refused', code: ATTACH_REQUIRED, retryable: false, file: filed.path };
|
|
367
|
+
}
|
|
368
|
+
if (await runtime.locked(room)) {
|
|
369
|
+
return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
|
|
370
|
+
}
|
|
371
|
+
const held = parts.map(({ path, parts: one }) => readParts(one, path).move);
|
|
372
|
+
if (parts.some(({ path }, at) => !covers(reach, room, path) || !moves(reach, room, held[at]))) {
|
|
373
|
+
throw unreached(room);
|
|
374
|
+
}
|
|
375
|
+
if (await intoLocked(held))
|
|
376
|
+
return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
|
|
377
|
+
return savedWithin(reach, room, await md.saveParts(room, parts, writtenBy));
|
|
378
|
+
},
|
|
379
|
+
async resolve(room, path, destination) {
|
|
380
|
+
const at = await runtime.located(room, path, destination);
|
|
381
|
+
return at !== null && !covers(reach, at.room, at.path)
|
|
382
|
+
? UNREADABLE
|
|
383
|
+
: md.resolve(room, path, destination);
|
|
384
|
+
},
|
|
385
|
+
history: async (room, path, query) => !covers(reach, room, path)
|
|
386
|
+
? failure({ kind: 'room-not-readable', locator: room })
|
|
387
|
+
: (await runtime.locked(room))
|
|
388
|
+
? lockedRoom(room)
|
|
389
|
+
: md.history(room, path, query),
|
|
390
|
+
binaryFile: {
|
|
391
|
+
read: async (room, path, revision) => !covers(reach, room, path)
|
|
392
|
+
? failure({ kind: 'room-not-readable', locator: room })
|
|
393
|
+
: (await runtime.locked(room))
|
|
394
|
+
? lockedRoom(room)
|
|
395
|
+
: hand(md.binaryFile.read(room, path, revision)),
|
|
396
|
+
link: async (room, path, revision) => !covers(reach, room, path)
|
|
397
|
+
? failure({ kind: 'room-not-readable', locator: room })
|
|
398
|
+
: (await runtime.locked(room))
|
|
399
|
+
? lockedRoom(room)
|
|
400
|
+
: md.binaryFile.link(room, path, revision),
|
|
401
|
+
show: async (room, path, revision) => !covers(reach, room, path)
|
|
402
|
+
? failure({ kind: 'room-not-readable', locator: room })
|
|
403
|
+
: (await runtime.locked(room))
|
|
404
|
+
? lockedRoom(room)
|
|
405
|
+
: hand(md.binaryFile.show(room, path, revision)),
|
|
406
|
+
attach: async (room, file) => !wholly(reach, room)
|
|
407
|
+
? failure({ kind: 'room-not-readable', locator: room })
|
|
408
|
+
: (await runtime.locked(room))
|
|
409
|
+
? lockedRoom(room)
|
|
410
|
+
: md.binaryFile.attach(room, file),
|
|
411
|
+
async part(file, part) {
|
|
412
|
+
const key = files.get(file);
|
|
413
|
+
if (key === undefined)
|
|
414
|
+
return null;
|
|
415
|
+
const bytes = await md.binaryFile.part(key, part);
|
|
416
|
+
if (bytes === null)
|
|
417
|
+
files.delete(file);
|
|
418
|
+
return bytes;
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
reached: async (room, roomVersion, path) => (path === undefined ? wholly(reach, room) : covers(reach, room, path)) &&
|
|
422
|
+
!(await runtime.locked(room)) &&
|
|
423
|
+
(await md.reached(room, roomVersion, path)),
|
|
235
424
|
log: () => (reach === null ? md.log() : ''),
|
|
425
|
+
cleanUp: () => (reach === null ? md.cleanUp() : Promise.reject(deviceUnreached())),
|
|
426
|
+
unsent: () => (reach === null ? md.unsent() : Promise.reject(deviceUnreached())),
|
|
427
|
+
reset: () => (reach === null ? md.reset() : Promise.reject(deviceUnreached())),
|
|
236
428
|
roomDraft: {
|
|
237
|
-
list: () => md.roomDraft.list()
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
429
|
+
list: async () => unlocked(draftsWithin(reach, await md.roomDraft.list()), await runtime.lockedRooms()),
|
|
430
|
+
async read(room, path) {
|
|
431
|
+
if (!covers(reach, room, path))
|
|
432
|
+
return null;
|
|
433
|
+
if (await runtime.locked(room))
|
|
434
|
+
throw lockedError(room);
|
|
435
|
+
return shownDraft(reach, await md.roomDraft.read(room, path));
|
|
436
|
+
},
|
|
437
|
+
async write(room, path, revision, parts, next) {
|
|
438
|
+
if (reach !== null && carriesFile(parts))
|
|
439
|
+
throw attachRequired();
|
|
440
|
+
const move = readParts(parts, path).move;
|
|
441
|
+
if (!covers(reach, room, path) || !moves(reach, room, move))
|
|
241
442
|
throw unreached(room);
|
|
242
|
-
|
|
243
|
-
|
|
443
|
+
if (await runtime.locked(room))
|
|
444
|
+
throw lockedError(room);
|
|
445
|
+
if (await intoLocked([move]))
|
|
446
|
+
throw lockedError(move.room);
|
|
447
|
+
return writeWithin(reach, await md.roomDraft.write(room, path, revision, parts, next));
|
|
244
448
|
},
|
|
245
449
|
async discard(room, path, revision) {
|
|
246
450
|
if (!covers(reach, room, path))
|
|
247
|
-
return
|
|
451
|
+
return staleWrite(null);
|
|
452
|
+
if (await runtime.locked(room))
|
|
453
|
+
throw lockedError(room);
|
|
248
454
|
return writeWithin(reach, await md.roomDraft.discard(room, path, revision));
|
|
249
455
|
},
|
|
250
456
|
},
|
|
@@ -252,15 +458,15 @@ export function narrowed(runtime, given) {
|
|
|
252
458
|
list: () => conversation().list(),
|
|
253
459
|
read: (id, options) => conversation().read(id, options),
|
|
254
460
|
create: (input) => conversation().create(input),
|
|
255
|
-
send: (id) => conversation().send(id),
|
|
461
|
+
send: (id, operation, draft) => conversation().send(id, operation, draft),
|
|
256
462
|
answer: (id, answer) => conversation().answer(id, answer),
|
|
257
463
|
interrupt: (id) => conversation().interrupt(id),
|
|
258
464
|
kill: (id) => conversation().kill(id),
|
|
259
|
-
cancel: (id, turn) => conversation().cancel(id, turn),
|
|
260
465
|
delete: (id) => conversation().delete(id),
|
|
261
466
|
},
|
|
262
467
|
chatDraft: {
|
|
263
|
-
write: (id, draft) => chatDraft().write(id, draft),
|
|
468
|
+
write: (id, draft, revision) => chatDraft().write(id, draft, revision),
|
|
469
|
+
prepend: (id, draft) => chatDraft().prepend(id, draft),
|
|
264
470
|
discard: (id) => chatDraft().discard(id),
|
|
265
471
|
},
|
|
266
472
|
observe: observing(within),
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { DocumentQuery, GroupQuery } from '@agentshouse/mdmodel';
|
|
2
|
+
import type { MdRuntimeClient } from './client.js';
|
|
3
|
+
import type { AppRoom, ConnectionRefusal, SavedRoomDraft, SaveResult } from './connection.js';
|
|
4
|
+
import type { Member, ServiceMode } from './adapter.js';
|
|
5
|
+
import type { ConversationAgent, ConversationEntry, ConversationOptions, ConversationView } from './conversation.js';
|
|
6
|
+
import { type RuntimeError } from './failure.js';
|
|
7
|
+
import type { QueueEntry } from './outcome.js';
|
|
8
|
+
import type { DocumentResult, GroupResult, PendingResult } from './query.js';
|
|
9
|
+
import type { Readiness } from './readiness.js';
|
|
10
|
+
import type { RoomDraft, RoomDraftParts } from './room-draft.js';
|
|
11
|
+
import type { SyncStatus } from './status.js';
|
|
12
|
+
type Client = MdRuntimeClient | null;
|
|
13
|
+
export type Observed<T> = {
|
|
14
|
+
readonly state: 'loading';
|
|
15
|
+
} | {
|
|
16
|
+
readonly state: 'ready';
|
|
17
|
+
readonly value: T;
|
|
18
|
+
};
|
|
19
|
+
export declare function useObserve(client: Client, topic: 'deployment-facts'): Observed<unknown>;
|
|
20
|
+
export declare function useObserve(client: Client, topic: 'rooms'): Observed<readonly AppRoom[]>;
|
|
21
|
+
export declare function useObserve(client: Client, topic: 'readiness'): Observed<Readiness>;
|
|
22
|
+
export declare function useObserve(client: Client, topic: 'connection-refusal'): Observed<ConnectionRefusal | null>;
|
|
23
|
+
export declare function useObserve(client: Client, topic: 'storage-loss'): Observed<string | null>;
|
|
24
|
+
export declare function useObserve(client: Client, topic: 'sync-status'): Observed<SyncStatus>;
|
|
25
|
+
export declare function useObserve(client: Client, topic: 'service-mode'): Observed<ServiceMode>;
|
|
26
|
+
export declare function useObserve(client: Client, topic: 'member'): Observed<Member | null>;
|
|
27
|
+
export declare function useObserve(client: Client, topic: 'authority-changes'): Observed<number>;
|
|
28
|
+
export declare function useObserve(client: Client, topic: 'query', query: DocumentQuery): Observed<DocumentResult | PendingResult>;
|
|
29
|
+
export declare function useObserve(client: Client, topic: 'query', query: GroupQuery): Observed<GroupResult | PendingResult>;
|
|
30
|
+
export declare function useObserve(client: Client, topic: 'queue', locator: string): Observed<readonly QueueEntry[]>;
|
|
31
|
+
export declare function useObserve(client: Client, topic: 'room-drafts'): Observed<readonly RoomDraft[]>;
|
|
32
|
+
export declare function useObserve(client: Client, topic: 'presentation'): Observed<unknown>;
|
|
33
|
+
export declare function useObserve(client: Client, topic: 'conversation', conversation: string, options: ConversationOptions): Observed<ConversationView>;
|
|
34
|
+
export declare function useObserve(client: Client, topic: 'conversations'): Observed<readonly ConversationEntry[]>;
|
|
35
|
+
export declare function useObserve(client: Client, topic: 'agents'): Observed<readonly ConversationAgent[]>;
|
|
36
|
+
export type Saving = {
|
|
37
|
+
readonly busy: boolean;
|
|
38
|
+
readonly outcome: SaveResult | null;
|
|
39
|
+
readonly failure: RuntimeError | null;
|
|
40
|
+
save(room: string, parts: ReadonlyMap<string, RoomDraftParts>): Promise<SaveResult>;
|
|
41
|
+
saveWritten(room: string, roomDrafts: readonly SavedRoomDraft[]): Promise<SaveResult>;
|
|
42
|
+
};
|
|
43
|
+
export declare function useSave(client: MdRuntimeClient): Saving;
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=react.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"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"}
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
|
2
|
+
import { failed } from './failure.js';
|
|
3
|
+
const LOADING = { state: 'loading' };
|
|
4
|
+
export function useObserve(client, topic, ...input) {
|
|
5
|
+
const key = JSON.stringify([topic, ...input]);
|
|
6
|
+
const [held, setHeld] = useState(null);
|
|
7
|
+
const opened = useRef(input);
|
|
8
|
+
opened.current = input;
|
|
9
|
+
useLayoutEffect(() => {
|
|
10
|
+
if (client === null)
|
|
11
|
+
return;
|
|
12
|
+
let live = true;
|
|
13
|
+
let observation = null;
|
|
14
|
+
const observe = client.observe;
|
|
15
|
+
void observe(topic, ...opened.current).then((open) => {
|
|
16
|
+
if (!live) {
|
|
17
|
+
open.close();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
observation = open;
|
|
21
|
+
const take = (value) => {
|
|
22
|
+
if (live)
|
|
23
|
+
setHeld({ client, key, observed: { state: 'ready', value } });
|
|
24
|
+
};
|
|
25
|
+
take(open.current());
|
|
26
|
+
open.subscribe(take);
|
|
27
|
+
});
|
|
28
|
+
return () => {
|
|
29
|
+
live = false;
|
|
30
|
+
observation?.close();
|
|
31
|
+
};
|
|
32
|
+
}, [client, key]);
|
|
33
|
+
return held !== null && held.client === client && held.key === key ? held.observed : LOADING;
|
|
34
|
+
}
|
|
35
|
+
const IDLE = { busy: false, outcome: null, failure: null };
|
|
36
|
+
export function useSave(client) {
|
|
37
|
+
const [state, setState] = useState(IDLE);
|
|
38
|
+
const settled = useCallback(async (saving) => {
|
|
39
|
+
setState({ busy: true, outcome: null, failure: null });
|
|
40
|
+
try {
|
|
41
|
+
const outcome = await saving();
|
|
42
|
+
setState({ busy: false, outcome, failure: null });
|
|
43
|
+
return outcome;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const failure = failed(error);
|
|
47
|
+
setState({ busy: false, outcome: null, failure });
|
|
48
|
+
throw failure;
|
|
49
|
+
}
|
|
50
|
+
}, []);
|
|
51
|
+
const save = useCallback((room, parts) => settled(() => client.saveParts(room, [...parts].map(([path, one]) => ({ path, parts: one })))), [client, settled]);
|
|
52
|
+
const saveWritten = useCallback((room, roomDrafts) => settled(() => client.save(room, roomDrafts)), [client, settled]);
|
|
53
|
+
return { ...state, save, saveWritten };
|
|
54
|
+
}
|