@agentshouse/mdruntime 0.1.0-c1cc928d1d256 → 0.1.0-c2aaa9c0e3ca4
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 +147 -101
- package/README.md +13 -3
- package/dist/adapter.d.ts +42 -14
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attempt.d.ts +16 -0
- package/dist/attempt.d.ts.map +1 -0
- package/dist/attempt.js +57 -0
- package/dist/binary.d.ts +23 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +17 -5
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +88 -38
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +19 -12
- package/dist/change.d.ts +15 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +26 -1
- package/dist/chat/acts.d.ts +34 -0
- package/dist/chat/acts.d.ts.map +1 -0
- package/dist/chat/acts.js +36 -0
- package/dist/chat/context.d.ts +3 -0
- package/dist/chat/context.d.ts.map +1 -0
- package/dist/chat/context.js +10 -0
- package/dist/chat/input.d.ts +9 -0
- package/dist/chat/input.d.ts.map +1 -0
- package/dist/chat/input.js +99 -0
- package/dist/chat/kit.d.ts +60 -0
- package/dist/chat/kit.d.ts.map +1 -0
- package/dist/chat/kit.js +123 -0
- package/dist/chat/lasting.d.ts +6 -0
- package/dist/chat/lasting.d.ts.map +1 -0
- package/dist/chat/lasting.js +19 -0
- package/dist/chat/list.d.ts +20 -0
- package/dist/chat/list.d.ts.map +1 -0
- package/dist/chat/list.js +59 -0
- package/dist/chat/markdown.d.ts +19 -0
- package/dist/chat/markdown.d.ts.map +1 -0
- package/dist/chat/markdown.js +187 -0
- package/dist/chat/message.d.ts +6 -0
- package/dist/chat/message.d.ts.map +1 -0
- package/dist/chat/message.js +192 -0
- package/dist/chat/messages.d.ts +21 -0
- package/dist/chat/messages.d.ts.map +1 -0
- package/dist/chat/messages.js +151 -0
- package/dist/chat/name.d.ts +6 -0
- package/dist/chat/name.d.ts.map +1 -0
- package/dist/chat/name.js +37 -0
- package/dist/chat/opened.d.ts +11 -0
- package/dist/chat/opened.d.ts.map +1 -0
- package/dist/chat/opened.js +17 -0
- package/dist/chat/overlay.d.ts +31 -0
- package/dist/chat/overlay.d.ts.map +1 -0
- package/dist/chat/overlay.js +97 -0
- package/dist/chat/parts.d.ts +57 -0
- package/dist/chat/parts.d.ts.map +1 -0
- package/dist/chat/parts.js +119 -0
- package/dist/chat/process.d.ts +20 -0
- package/dist/chat/process.d.ts.map +1 -0
- package/dist/chat/process.js +32 -0
- package/dist/chat/provider.d.ts +63 -0
- package/dist/chat/provider.d.ts.map +1 -0
- package/dist/chat/provider.js +645 -0
- package/dist/chat/question.d.ts +9 -0
- package/dist/chat/question.d.ts.map +1 -0
- package/dist/chat/question.js +80 -0
- package/dist/chat/questions.d.ts +31 -0
- package/dist/chat/questions.d.ts.map +1 -0
- package/dist/chat/questions.js +111 -0
- package/dist/chat/read.d.ts +5 -0
- package/dist/chat/read.d.ts.map +1 -0
- package/dist/chat/read.js +13 -0
- package/dist/chat/ready.d.ts +33 -0
- package/dist/chat/ready.d.ts.map +1 -0
- package/dist/chat/ready.js +91 -0
- package/dist/chat/style.d.ts +4 -0
- package/dist/chat/style.d.ts.map +1 -0
- package/dist/chat/style.js +302 -0
- package/dist/chat.d.ts +14 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +8 -0
- package/dist/client.d.ts +12 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +30 -15
- package/dist/collection.d.ts +6 -11
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +56 -19
- package/dist/connection.d.ts +20 -9
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +422 -131
- package/dist/conversation.d.ts +11 -3
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +47 -7
- 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/host.d.ts +1 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +9 -9
- 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 +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/memory.d.ts +3 -3
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +53 -20
- package/dist/model.d.ts +3 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/open.d.ts +2 -0
- package/dist/open.d.ts.map +1 -0
- package/dist/open.js +1 -0
- package/dist/outcome.d.ts +28 -3
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +41 -1
- package/dist/overlay.d.ts +14 -3
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +75 -37
- package/dist/query.d.ts +4 -0
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -0
- package/dist/reach.d.ts +2 -0
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +43 -19
- package/dist/react.d.ts +4 -2
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +3 -2
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +9 -2
- package/dist/refresh.d.ts +10 -0
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +42 -38
- package/dist/replica.d.ts +7 -4
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +74 -54
- package/dist/room-draft.d.ts +26 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +132 -23
- package/dist/rows.d.ts +5 -1
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +10 -2
- package/dist/save.d.ts +27 -1
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +340 -33
- package/dist/search.d.ts +3 -2
- package/dist/search.d.ts.map +1 -1
- package/dist/status.d.ts +2 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +0 -8
- package/dist/store.d.ts +31 -10
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +261 -163
- package/dist/wire.d.ts +6 -3
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +14 -2
- package/package.json +22 -14
- package/dist/attachment.d.ts +0 -3
- package/dist/attachment.d.ts.map +0 -1
- package/dist/attachment.js +0 -8
package/dist/connection.js
CHANGED
|
@@ -3,17 +3,22 @@ import { cached, modelTypes, NO_MODEL, sameModel, } from './model.js';
|
|
|
3
3
|
import { observing } from './observation.js';
|
|
4
4
|
import { filterOf, identityOf, planOf, } from './query.js';
|
|
5
5
|
import { createReadSide } from './read-side.js';
|
|
6
|
+
import { markdownPath, roomOfKey } from './rows.js';
|
|
6
7
|
import { createSearchIndex, searchPlanOf, } from './search.js';
|
|
7
8
|
import { bootstrapRoom } from './bootstrap.js';
|
|
8
9
|
import { changeSet, mergeChangeSets } from './change-set.js';
|
|
9
10
|
import { createConversationStore, } from './conversation.js';
|
|
10
|
-
import {
|
|
11
|
+
import { queuedFile } from './change.js';
|
|
12
|
+
import { validRoot } from './collection.js';
|
|
13
|
+
import { runtimeError } from './failure.js';
|
|
11
14
|
import { createLog } from './log.js';
|
|
15
|
+
import { binaryRecord } from './binary.js';
|
|
16
|
+
import { absentBinary, binaryAnswer, calledResult, historyAnswer, undeclared, UNCERTAIN, } from './outcome.js';
|
|
12
17
|
import { refreshRoom } from './refresh.js';
|
|
13
|
-
import { buildBatch } from './save.js';
|
|
14
|
-
import { applyReturned, compositionOf,
|
|
15
|
-
import { changedParts, changesOf, held, readParts, returnedInto, underneath, } from './room-draft.js';
|
|
16
|
-
import {
|
|
18
|
+
import { buildBatch, joined, limitedIn, requeued } from './save.js';
|
|
19
|
+
import { applyReturned, compositionOf, confirmedOf, landed, readReclassified, replicaOf, sameMembership, withdrawals, } from './replica.js';
|
|
20
|
+
import { binaryBase, changedParts, changesOf, held, heldFile, joinedDraft, readParts, returnedBy, returnedInto, staleWrite, underneath, withdrawnDrafts, } from './room-draft.js';
|
|
21
|
+
import { same, saving, STARTING, transferring, } from './status.js';
|
|
17
22
|
import { detectStorageLoss, nextGeneration } from './durability.js';
|
|
18
23
|
import { roomDraftRevision } from './identity.js';
|
|
19
24
|
import { beneath, normalize } from './paths.js';
|
|
@@ -34,16 +39,19 @@ function declaredSelection(room) {
|
|
|
34
39
|
selection.roots.every((other, at) => at === index || !within(other, root)));
|
|
35
40
|
}
|
|
36
41
|
export function composeRuntime(options) {
|
|
42
|
+
const localSaves = options.adapter.localSaves === true;
|
|
37
43
|
let authorization = options.authorization;
|
|
38
44
|
let opening;
|
|
39
45
|
let store;
|
|
40
46
|
const replicas = new Map();
|
|
47
|
+
const queuesWithoutReplica = new Map();
|
|
41
48
|
const pendings = new Map();
|
|
42
49
|
const roomDrafts = new Map();
|
|
43
50
|
const presentations = new Map();
|
|
44
51
|
let limits = [];
|
|
45
52
|
let capabilities = [];
|
|
46
53
|
const rooms = new Map();
|
|
54
|
+
const counted = new Map();
|
|
47
55
|
const unreadable = new Set();
|
|
48
56
|
const models = new Map();
|
|
49
57
|
const liveModels = new Map();
|
|
@@ -82,6 +90,7 @@ export function composeRuntime(options) {
|
|
|
82
90
|
let withdrawStream;
|
|
83
91
|
const announced = new Map();
|
|
84
92
|
let underway = null;
|
|
93
|
+
let streamed = null;
|
|
85
94
|
let handed = false;
|
|
86
95
|
const submitting = new Map();
|
|
87
96
|
const answering = new Map();
|
|
@@ -107,9 +116,12 @@ export function composeRuntime(options) {
|
|
|
107
116
|
const serviceModeListeners = new Set();
|
|
108
117
|
let member = null;
|
|
109
118
|
const memberListeners = new Set();
|
|
119
|
+
const materialized = new Set();
|
|
120
|
+
let everyRoom = false;
|
|
121
|
+
const transfers = new Map();
|
|
110
122
|
const conversations = createConversationStore({
|
|
111
123
|
agents: declaredAgents,
|
|
112
|
-
documents: (locator) =>
|
|
124
|
+
documents: (locator) => rowsQuery(planOf({ rooms: [locator], select: 'all' }, roomFacts())),
|
|
113
125
|
call: (name, input) => invokeAction(name, input),
|
|
114
126
|
durably,
|
|
115
127
|
stream: () => options.adapter.conversations,
|
|
@@ -185,7 +197,9 @@ export function composeRuntime(options) {
|
|
|
185
197
|
}
|
|
186
198
|
}
|
|
187
199
|
function pending() {
|
|
188
|
-
return
|
|
200
|
+
return (!localSaves &&
|
|
201
|
+
(queuesWithoutReplica.size > 0 ||
|
|
202
|
+
[...replicas.values()].some((replica) => replica.queue.length > 0)));
|
|
189
203
|
}
|
|
190
204
|
function attention() {
|
|
191
205
|
const attentionRequired = [];
|
|
@@ -214,8 +228,24 @@ export function composeRuntime(options) {
|
|
|
214
228
|
}
|
|
215
229
|
return { attentionRequired, reset };
|
|
216
230
|
}
|
|
231
|
+
function documentsIn(locator) {
|
|
232
|
+
const replica = replicas.get(locator);
|
|
233
|
+
if (replica === undefined)
|
|
234
|
+
return 0;
|
|
235
|
+
const roots = replica.confirmed.record.typedSelection?.roots ?? [];
|
|
236
|
+
let count = 0;
|
|
237
|
+
for (const path of Object.keys(replica.visible.documents)) {
|
|
238
|
+
if (markdownPath(path) && !roots.some((root) => beneath(root, path)))
|
|
239
|
+
count += 1;
|
|
240
|
+
}
|
|
241
|
+
return count;
|
|
242
|
+
}
|
|
217
243
|
function listed() {
|
|
218
|
-
return structuredClone([...rooms.values()])
|
|
244
|
+
return structuredClone([...rooms.values()].map((room) => {
|
|
245
|
+
const documents = documentsIn(room.locator);
|
|
246
|
+
counted.set(room.locator, documents);
|
|
247
|
+
return { ...room, documents };
|
|
248
|
+
}));
|
|
219
249
|
}
|
|
220
250
|
function roomFacts(reach = null) {
|
|
221
251
|
const held = [...rooms.values()]
|
|
@@ -270,6 +300,8 @@ export function composeRuntime(options) {
|
|
|
270
300
|
listener(shown);
|
|
271
301
|
}
|
|
272
302
|
function entryOf(batch) {
|
|
303
|
+
if (localSaves)
|
|
304
|
+
return Object.freeze({ batch, state: 'local' });
|
|
273
305
|
const state = pendings.get(batch.id);
|
|
274
306
|
if (state?.accepted !== undefined) {
|
|
275
307
|
return Object.freeze({ batch, state: 'accepted', roomVersion: state.accepted });
|
|
@@ -277,10 +309,7 @@ export function composeRuntime(options) {
|
|
|
277
309
|
return Object.freeze({ batch, ...(state?.submission ?? { state: 'waiting' }) });
|
|
278
310
|
}
|
|
279
311
|
function entriesOf(locator) {
|
|
280
|
-
|
|
281
|
-
if (replica === undefined)
|
|
282
|
-
return [];
|
|
283
|
-
return replica.queue.map(entryOf);
|
|
312
|
+
return (replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? []).map(entryOf);
|
|
284
313
|
}
|
|
285
314
|
function emitQueue(locator) {
|
|
286
315
|
const entries = entriesOf(locator);
|
|
@@ -290,12 +319,47 @@ export function composeRuntime(options) {
|
|
|
290
319
|
}
|
|
291
320
|
}
|
|
292
321
|
function applyChangeSet(set) {
|
|
322
|
+
const held = everyRoom
|
|
323
|
+
? set
|
|
324
|
+
: { ...set, documents: set.documents.filter(({ locator }) => materialized.has(locator)) };
|
|
325
|
+
readSide.apply(held);
|
|
326
|
+
searchIndex.changed(held);
|
|
327
|
+
}
|
|
328
|
+
function materialize(locators) {
|
|
329
|
+
if (everyRoom)
|
|
330
|
+
return;
|
|
331
|
+
if (locators === null)
|
|
332
|
+
everyRoom = true;
|
|
333
|
+
const sets = [];
|
|
334
|
+
for (const locator of locators ?? [...replicas.keys()]) {
|
|
335
|
+
if (materialized.has(locator))
|
|
336
|
+
continue;
|
|
337
|
+
materialized.add(locator);
|
|
338
|
+
const replica = replicas.get(locator);
|
|
339
|
+
if (replica !== undefined)
|
|
340
|
+
sets.push(changeSet(locator, null, replica));
|
|
341
|
+
}
|
|
342
|
+
if (sets.length === 0)
|
|
343
|
+
return;
|
|
344
|
+
const set = mergeChangeSets(sets);
|
|
293
345
|
readSide.apply(set);
|
|
294
346
|
searchIndex.changed(set);
|
|
295
347
|
}
|
|
348
|
+
function reachedRooms(plan) {
|
|
349
|
+
if (plan.rooms !== null)
|
|
350
|
+
return plan.rooms;
|
|
351
|
+
if (plan.reach === null)
|
|
352
|
+
return null;
|
|
353
|
+
return [...plan.reach.rooms, ...plan.reach.documents.map(roomOfKey)];
|
|
354
|
+
}
|
|
355
|
+
function rowsQuery(plan) {
|
|
356
|
+
materialize(reachedRooms(plan));
|
|
357
|
+
return readSide.query(plan);
|
|
358
|
+
}
|
|
296
359
|
function publishChangeSet(set) {
|
|
297
360
|
applyChangeSet(set);
|
|
298
|
-
if (set.rooms.length > 0
|
|
361
|
+
if (set.rooms.length > 0 ||
|
|
362
|
+
[...new Set(set.documents.map(({ locator }) => locator))].some((locator) => rooms.has(locator) && counted.get(locator) !== documentsIn(locator)))
|
|
299
363
|
emitRooms();
|
|
300
364
|
for (const locator of set.queues)
|
|
301
365
|
emitQueue(locator);
|
|
@@ -342,10 +406,7 @@ export function composeRuntime(options) {
|
|
|
342
406
|
const revoked = { status: 'unavailable', reason: 'revoked' };
|
|
343
407
|
try {
|
|
344
408
|
const local = await openLocal();
|
|
345
|
-
await local.revokeModels([...replicas.values()].map((replica) =>
|
|
346
|
-
record: replica.confirmed.record,
|
|
347
|
-
queue: replica.queue,
|
|
348
|
-
})));
|
|
409
|
+
await local.revokeModels([...replicas.values()].map((replica) => replica.confirmed.record));
|
|
349
410
|
}
|
|
350
411
|
catch {
|
|
351
412
|
journal.record('model revocation persistence failed');
|
|
@@ -369,10 +430,7 @@ export function composeRuntime(options) {
|
|
|
369
430
|
model: revoked,
|
|
370
431
|
selectionModel: revoked,
|
|
371
432
|
};
|
|
372
|
-
|
|
373
|
-
replicas.set(locator, read.kind === 'staged'
|
|
374
|
-
? replicaOf(read.confirmed, before.queue, before)
|
|
375
|
-
: { ...before, confirmed: { ...before.confirmed, record } });
|
|
433
|
+
replicas.set(locator, replicaOf(confirmedOf(record, before.confirmed), before.queue, before));
|
|
376
434
|
}
|
|
377
435
|
rooms.set(locator, {
|
|
378
436
|
...room,
|
|
@@ -389,7 +447,8 @@ export function composeRuntime(options) {
|
|
|
389
447
|
}
|
|
390
448
|
finally {
|
|
391
449
|
refusal = {
|
|
392
|
-
reason,
|
|
450
|
+
code: reason,
|
|
451
|
+
retryable: false,
|
|
393
452
|
reauthorizationOffered: reason === 'revoked-authorization',
|
|
394
453
|
refusal: provider,
|
|
395
454
|
};
|
|
@@ -414,7 +473,6 @@ export function composeRuntime(options) {
|
|
|
414
473
|
},
|
|
415
474
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
416
475
|
roomVersion: replicas.get(room.locator)?.confirmed.record.roomVersion ?? null,
|
|
417
|
-
attachments: replicas.get(room.locator)?.confirmed.record.attachments ?? [],
|
|
418
476
|
synchronizationAvailable,
|
|
419
477
|
refusal: scopedRefusals.get(room.locator) ?? sourceRefusals.get(room.locator) ?? null,
|
|
420
478
|
});
|
|
@@ -439,7 +497,14 @@ export function composeRuntime(options) {
|
|
|
439
497
|
emitRooms();
|
|
440
498
|
}
|
|
441
499
|
function unlistedHeld(listed) {
|
|
442
|
-
return [
|
|
500
|
+
return [
|
|
501
|
+
...new Set([
|
|
502
|
+
...rooms.keys(),
|
|
503
|
+
...roomDrafts.keys(),
|
|
504
|
+
...transfers.keys(),
|
|
505
|
+
...queuesWithoutReplica.keys(),
|
|
506
|
+
]),
|
|
507
|
+
].filter((locator) => !listed.has(locator));
|
|
443
508
|
}
|
|
444
509
|
async function dropUnlisted(listed, local) {
|
|
445
510
|
if (unlistedHeld(listed).length === 0)
|
|
@@ -453,11 +518,13 @@ export function composeRuntime(options) {
|
|
|
453
518
|
drafts: [...(roomDrafts.get(locator)?.keys() ?? [])],
|
|
454
519
|
}));
|
|
455
520
|
for (const locator of unlisted) {
|
|
456
|
-
for (const batch of replicas.get(locator)?.queue ?? [])
|
|
521
|
+
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [])
|
|
457
522
|
pendings.delete(batch.id);
|
|
458
523
|
replicas.delete(locator);
|
|
524
|
+
queuesWithoutReplica.delete(locator);
|
|
459
525
|
rooms.delete(locator);
|
|
460
526
|
roomDrafts.delete(locator);
|
|
527
|
+
transfers.delete(locator);
|
|
461
528
|
unreadable.delete(locator);
|
|
462
529
|
stale.delete(locator);
|
|
463
530
|
scopedRefusals.delete(locator);
|
|
@@ -494,7 +561,7 @@ export function composeRuntime(options) {
|
|
|
494
561
|
return model;
|
|
495
562
|
}
|
|
496
563
|
async function hydrate(opened) {
|
|
497
|
-
const [records, queues, kept, presented, held, chatDrafts, lastCompleted, declared, named, known,] = await Promise.all([
|
|
564
|
+
const [records, queues, kept, presented, held, chatDrafts, lastCompleted, declared, named, known, staged,] = await Promise.all([
|
|
498
565
|
opened.all(),
|
|
499
566
|
opened.queues(),
|
|
500
567
|
opened.roomDrafts(),
|
|
@@ -505,7 +572,11 @@ export function composeRuntime(options) {
|
|
|
505
572
|
opened.providerLimits(),
|
|
506
573
|
opened.capabilities(),
|
|
507
574
|
opened.member(),
|
|
575
|
+
opened.transfers(),
|
|
508
576
|
]);
|
|
577
|
+
transfers.clear();
|
|
578
|
+
for (const record of staged)
|
|
579
|
+
transfers.set(record.locator, record);
|
|
509
580
|
limits = declared;
|
|
510
581
|
capabilities = named;
|
|
511
582
|
member = known;
|
|
@@ -524,18 +595,17 @@ export function composeRuntime(options) {
|
|
|
524
595
|
}
|
|
525
596
|
const previousReplicas = new Map(replicas);
|
|
526
597
|
replicas.clear();
|
|
598
|
+
queuesWithoutReplica.clear();
|
|
599
|
+
const replicated = new Set(records.map((record) => record.locator));
|
|
600
|
+
for (const [locator, queue] of queuedBatches) {
|
|
601
|
+
if (!replicated.has(locator))
|
|
602
|
+
queuesWithoutReplica.set(locator, queue);
|
|
603
|
+
}
|
|
527
604
|
for (const record of records) {
|
|
528
605
|
const model = restored(modelKey(record.locator, 'collection'), record.model);
|
|
529
606
|
const selectionModel = restored(modelKey(record.locator, 'typed-selection'), record.selectionModel);
|
|
530
607
|
const prior = previousReplicas.get(record.locator);
|
|
531
|
-
const
|
|
532
|
-
const read = readConfirmed(retyped, prior?.confirmed);
|
|
533
|
-
const confirmed = read.kind === 'staged'
|
|
534
|
-
? read.confirmed
|
|
535
|
-
: {
|
|
536
|
-
record: retyped,
|
|
537
|
-
collection: parseCollection(retyped.documents, compositionOf(retyped)),
|
|
538
|
-
};
|
|
608
|
+
const confirmed = confirmedOf({ ...record, model, selectionModel }, prior?.confirmed);
|
|
539
609
|
replicas.set(record.locator, replicaOf(confirmed, queuedBatches.get(record.locator) ?? [], prior));
|
|
540
610
|
const existing = rooms.get(record.locator);
|
|
541
611
|
if (existing === undefined)
|
|
@@ -544,7 +614,6 @@ export function composeRuntime(options) {
|
|
|
544
614
|
rooms.set(record.locator, {
|
|
545
615
|
...existing,
|
|
546
616
|
roomVersion: record.roomVersion,
|
|
547
|
-
attachments: record.attachments,
|
|
548
617
|
operations: record.operations,
|
|
549
618
|
replica: record.replica,
|
|
550
619
|
model: modelTypes(model),
|
|
@@ -556,10 +625,6 @@ export function composeRuntime(options) {
|
|
|
556
625
|
},
|
|
557
626
|
});
|
|
558
627
|
}
|
|
559
|
-
if (read.kind === 'refused') {
|
|
560
|
-
refusedSource(record, read);
|
|
561
|
-
declarationRefusals.add(record.locator);
|
|
562
|
-
}
|
|
563
628
|
}
|
|
564
629
|
return {
|
|
565
630
|
lastCompleted,
|
|
@@ -621,11 +686,12 @@ export function composeRuntime(options) {
|
|
|
621
686
|
return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
|
|
622
687
|
}
|
|
623
688
|
async function settle(local, locator, before, confirmed, queue, settling = {}) {
|
|
624
|
-
const
|
|
689
|
+
const previousQueue = before?.queue ?? queuesWithoutReplica.get(locator) ?? [];
|
|
690
|
+
const withdrawn = withdrawals(confirmed, queue, withdrawable, localSaves);
|
|
625
691
|
const kept = queue.filter((batch) => !withdrawn.includes(batch));
|
|
626
692
|
const unqueued = withdrawn.map((batch) => ({
|
|
627
693
|
batch,
|
|
628
|
-
returned: landed(batch, confirmed) ? null :
|
|
694
|
+
returned: localSaves || landed(batch, confirmed) ? null : returnedBy('withdrawn'),
|
|
629
695
|
}));
|
|
630
696
|
const returning = [
|
|
631
697
|
...[...unqueued].reverse(),
|
|
@@ -646,26 +712,44 @@ export function composeRuntime(options) {
|
|
|
646
712
|
});
|
|
647
713
|
}
|
|
648
714
|
}
|
|
649
|
-
await local.
|
|
650
|
-
|
|
651
|
-
previous: before?.confirmed.record ?? null,
|
|
652
|
-
queue: kept,
|
|
653
|
-
previousQueue: before?.queue ?? [],
|
|
654
|
-
roomDrafts: [...written.values()],
|
|
655
|
-
});
|
|
656
|
-
for (const batch of before?.queue ?? []) {
|
|
715
|
+
await local.keepAuthored({ locator, queue: kept, roomDrafts: [...written.values()] });
|
|
716
|
+
for (const batch of previousQueue) {
|
|
657
717
|
if (!kept.includes(batch))
|
|
658
718
|
pendings.delete(batch.id);
|
|
659
719
|
}
|
|
660
720
|
for (const record of written.values())
|
|
661
721
|
rememberRoomDraft(record);
|
|
722
|
+
try {
|
|
723
|
+
await local.write({
|
|
724
|
+
next: confirmed.record,
|
|
725
|
+
previous: before?.confirmed.record ?? null,
|
|
726
|
+
...(settling.stored === undefined ? {} : { stored: settling.stored }),
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
catch (error) {
|
|
730
|
+
const held = before === undefined ? null : replicaOf(before.confirmed, kept, before);
|
|
731
|
+
if (held !== null)
|
|
732
|
+
replicas.set(locator, held);
|
|
733
|
+
else if (kept.length > 0)
|
|
734
|
+
queuesWithoutReplica.set(locator, kept);
|
|
735
|
+
else
|
|
736
|
+
queuesWithoutReplica.delete(locator);
|
|
737
|
+
publishChangeSet(changeSet(locator, before ?? null, held, {
|
|
738
|
+
queue: kept.length !== previousQueue.length,
|
|
739
|
+
drafts: [...written.keys()],
|
|
740
|
+
}));
|
|
741
|
+
moveTo({ ...status, ...attention() });
|
|
742
|
+
throw error;
|
|
743
|
+
}
|
|
744
|
+
queuesWithoutReplica.delete(locator);
|
|
662
745
|
for (const { batch, returned } of unqueued) {
|
|
663
|
-
|
|
746
|
+
const outcome = localSaves ? 'dropped' : returned === null ? 'landed' : 'withdrawn';
|
|
747
|
+
journal.record(`outcome ${locator} ${batch.id} ${outcome}`);
|
|
664
748
|
}
|
|
665
749
|
replicas.set(locator, replicaOf(confirmed, kept, before));
|
|
666
750
|
return changeSet(locator, before ?? null, replicas.get(locator) ?? null, {
|
|
667
751
|
room: settling.room,
|
|
668
|
-
queue: kept.length !==
|
|
752
|
+
queue: kept.length !== previousQueue.length,
|
|
669
753
|
drafts: [...written.keys()],
|
|
670
754
|
});
|
|
671
755
|
}
|
|
@@ -700,7 +784,8 @@ export function composeRuntime(options) {
|
|
|
700
784
|
selection: taken(answers?.selection, held.selection, 'typed-selection'),
|
|
701
785
|
};
|
|
702
786
|
}
|
|
703
|
-
function publish(local, room, confirmed,
|
|
787
|
+
function publish(local, room, confirmed, landing = {}) {
|
|
788
|
+
const { base, stored } = landing;
|
|
704
789
|
return exclusive(async () => {
|
|
705
790
|
if (!continuing())
|
|
706
791
|
return true;
|
|
@@ -711,13 +796,15 @@ export function composeRuntime(options) {
|
|
|
711
796
|
return false;
|
|
712
797
|
}
|
|
713
798
|
adoptModels(room.locator, confirmed.record);
|
|
714
|
-
const queue = prior?.queue ?? [];
|
|
799
|
+
const queue = prior?.queue ?? queuesWithoutReplica.get(room.locator) ?? [];
|
|
715
800
|
const roomVersion = confirmed.record.roomVersion;
|
|
716
801
|
const accepted = queue.filter((batch) => {
|
|
717
802
|
const accepted = pendings.get(batch.id)?.accepted;
|
|
718
803
|
return (accepted !== undefined && options.adapter.reached(room.locator, roomVersion, accepted));
|
|
719
804
|
});
|
|
720
|
-
const set = await settle(local, room.locator, prior, confirmed, queue.filter((batch) => !accepted.includes(batch)), { room: true });
|
|
805
|
+
const set = await settle(local, room.locator, prior, confirmed, queue.filter((batch) => !accepted.includes(batch)), { room: true, ...(stored === undefined ? {} : { stored }) });
|
|
806
|
+
if (stored !== undefined)
|
|
807
|
+
transfers.delete(room.locator);
|
|
721
808
|
for (const batch of accepted)
|
|
722
809
|
pendings.delete(batch.id);
|
|
723
810
|
stale.delete(room.locator);
|
|
@@ -736,22 +823,40 @@ export function composeRuntime(options) {
|
|
|
736
823
|
const replica = replicas.get(locator);
|
|
737
824
|
const room = rooms.get(locator);
|
|
738
825
|
if (replica === undefined || room === undefined) {
|
|
739
|
-
throw
|
|
826
|
+
throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
|
|
740
827
|
}
|
|
741
828
|
const saved = [];
|
|
742
829
|
for (const { path, revision } of named) {
|
|
743
830
|
const record = roomDrafts.get(locator)?.get(path);
|
|
744
831
|
if (record === undefined || record.revision !== revision) {
|
|
745
|
-
return { outcome: 'refused',
|
|
832
|
+
return { outcome: 'refused', code: 'stale', retryable: false, file: path };
|
|
746
833
|
}
|
|
747
834
|
if (!saved.some((draft) => draft.path === path))
|
|
748
835
|
saved.push({ path, parts: record.parts });
|
|
749
836
|
}
|
|
750
837
|
const changed = saved.flatMap(({ path, parts }) => changedParts(path, underneath(parts, path, replica.visible)));
|
|
751
838
|
if (changed.length > 0)
|
|
752
|
-
return { outcome: 'refused',
|
|
839
|
+
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
753
840
|
const parts = saved.map(({ path, parts }) => changesOf(path, parts));
|
|
754
|
-
const
|
|
841
|
+
const changes = parts.flatMap(({ changes }) => changes);
|
|
842
|
+
const requeue = requeued(replica.queue, changes, replica.visible, withdrawable, (queue) => replicaOf(replica.confirmed, queue, replica).visible);
|
|
843
|
+
if (requeue.kind === 'refused')
|
|
844
|
+
return { outcome: 'refused', ...requeue.refusal };
|
|
845
|
+
const foreign = [...requeue.withdrawn.keys()].some((origin) => {
|
|
846
|
+
const writer = replica.queue.find((batch) => batch.id === origin)?.writtenBy;
|
|
847
|
+
return writer?.app !== writtenBy?.app || writer?.release !== writtenBy?.release;
|
|
848
|
+
});
|
|
849
|
+
if (foreign) {
|
|
850
|
+
return {
|
|
851
|
+
outcome: 'refused',
|
|
852
|
+
code: 'unknown-revision',
|
|
853
|
+
retryable: false,
|
|
854
|
+
file: [...requeue.queued].at(-1),
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
const withdrawing = requeue.withdrawn.size > 0;
|
|
858
|
+
const input = {
|
|
859
|
+
local: localSaves,
|
|
755
860
|
baseRoomVersion: replica.confirmed.record.roomVersion,
|
|
756
861
|
composition: compositionFor(replica.confirmed.record),
|
|
757
862
|
operations: room.operations,
|
|
@@ -759,29 +864,86 @@ export function composeRuntime(options) {
|
|
|
759
864
|
providerLimits: limits,
|
|
760
865
|
handle: room.handle,
|
|
761
866
|
visible: replica.visible,
|
|
762
|
-
changes
|
|
867
|
+
changes,
|
|
763
868
|
bases: parts.flatMap(({ bases }) => bases),
|
|
764
869
|
writtenBy,
|
|
870
|
+
};
|
|
871
|
+
const checked = withdrawing ? buildBatch({ ...input, queued: requeue.queued }) : null;
|
|
872
|
+
if (checked?.kind === 'refused')
|
|
873
|
+
return { outcome: 'refused', ...checked.refusal };
|
|
874
|
+
const owners = [...requeue.owners].flatMap((origin) => {
|
|
875
|
+
const batch = requeue.withdrawn.get(origin);
|
|
876
|
+
return batch === undefined || batch === null ? [] : [{ origin, batch }];
|
|
765
877
|
});
|
|
766
|
-
if (
|
|
878
|
+
if (owners.length > 1) {
|
|
879
|
+
return {
|
|
880
|
+
outcome: 'refused',
|
|
881
|
+
code: 'unknown-revision',
|
|
882
|
+
retryable: false,
|
|
883
|
+
file: [...requeue.queued].at(-1),
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
const built = !withdrawing
|
|
887
|
+
? buildBatch(input)
|
|
888
|
+
: requeue.changes.length === 0
|
|
889
|
+
? null
|
|
890
|
+
: buildBatch({
|
|
891
|
+
...input,
|
|
892
|
+
visible: replicaOf(replica.confirmed, requeue.queue, replica).visible,
|
|
893
|
+
changes: requeue.changes,
|
|
894
|
+
});
|
|
895
|
+
if (built?.kind === 'refused')
|
|
767
896
|
return { outcome: 'refused', ...built.refusal };
|
|
768
|
-
const
|
|
897
|
+
const [owner] = owners;
|
|
898
|
+
const join = built === null || owner === undefined
|
|
899
|
+
? null
|
|
900
|
+
: joined(requeue.queue, owner.batch, built.batch);
|
|
901
|
+
if (join?.kind === 'refused')
|
|
902
|
+
return { outcome: 'refused', ...join.refusal };
|
|
903
|
+
const withdrawn = new Map(requeue.withdrawn);
|
|
904
|
+
if (join !== null && owner !== undefined)
|
|
905
|
+
withdrawn.set(owner.origin, join.batch);
|
|
906
|
+
const drafted = new Map();
|
|
907
|
+
for (const [origin, replaced] of withdrawn) {
|
|
908
|
+
if (replaced === null)
|
|
909
|
+
continue;
|
|
910
|
+
const carried = withdrawnDrafts(pendings.get(origin)?.drafts ?? [], changes);
|
|
911
|
+
drafted.set(replaced.id, replaced === join?.batch
|
|
912
|
+
? [
|
|
913
|
+
...carried.map((held) => joinedDraft(held, saved)),
|
|
914
|
+
...saved.filter((draft) => !carried.some((held) => held.path === draft.path)),
|
|
915
|
+
]
|
|
916
|
+
: carried);
|
|
917
|
+
}
|
|
918
|
+
if (built !== null && join === null)
|
|
919
|
+
drafted.set(built.batch.id, saved);
|
|
920
|
+
const batches = join !== null ? join.queue : built === null ? requeue.queue : [...requeue.queue, built.batch];
|
|
921
|
+
for (const replaced of withdrawn.values()) {
|
|
922
|
+
if (replaced === null || !batches.includes(replaced))
|
|
923
|
+
continue;
|
|
924
|
+
const limited = limitedIn(batches, replaced, limits, (queue) => replicaOf(replica.confirmed, queue, replica).visible);
|
|
925
|
+
if (limited !== null)
|
|
926
|
+
return { outcome: 'refused', ...limited };
|
|
927
|
+
}
|
|
769
928
|
const consumed = saved.map(({ path }) => path);
|
|
770
929
|
try {
|
|
771
|
-
await local.
|
|
772
|
-
|
|
773
|
-
previous: replica.confirmed.record,
|
|
930
|
+
await local.keepAuthored({
|
|
931
|
+
locator,
|
|
774
932
|
queue: batches,
|
|
775
|
-
|
|
776
|
-
saved,
|
|
933
|
+
saved: drafted,
|
|
777
934
|
consumedRoomDrafts: consumed,
|
|
778
935
|
});
|
|
779
936
|
}
|
|
780
937
|
catch {
|
|
781
|
-
return { outcome: 'refused',
|
|
938
|
+
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
782
939
|
}
|
|
783
|
-
|
|
784
|
-
|
|
940
|
+
for (const origin of withdrawn.keys())
|
|
941
|
+
pendings.delete(origin);
|
|
942
|
+
for (const [id, drafts] of drafted) {
|
|
943
|
+
pendings.set(id, { drafts, submission: { state: 'waiting' }, lost: false });
|
|
944
|
+
}
|
|
945
|
+
if (!localSaves)
|
|
946
|
+
recorded = false;
|
|
785
947
|
for (const path of consumed)
|
|
786
948
|
forgetRoomDraft(locator, path);
|
|
787
949
|
replicas.set(locator, replicaOf(replica.confirmed, batches, replica));
|
|
@@ -789,9 +951,12 @@ export function composeRuntime(options) {
|
|
|
789
951
|
queue: true,
|
|
790
952
|
drafts: consumed,
|
|
791
953
|
}));
|
|
792
|
-
moveTo({ ...saving(status), ...attention() });
|
|
954
|
+
moveTo({ ...(localSaves ? status : saving(status)), ...attention() });
|
|
793
955
|
submitLater(locator);
|
|
794
|
-
|
|
956
|
+
const identifier = join?.batch.id ??
|
|
957
|
+
built?.batch.id ??
|
|
958
|
+
[...requeue.withdrawn].map(([origin, replaced]) => replaced?.id ?? origin).at(-1);
|
|
959
|
+
return { outcome: 'saved', identifier };
|
|
795
960
|
}
|
|
796
961
|
async function mark(local, locator, identifier, submission, lost) {
|
|
797
962
|
const state = pendings.get(identifier);
|
|
@@ -817,16 +982,15 @@ export function composeRuntime(options) {
|
|
|
817
982
|
}
|
|
818
983
|
async function readHistory(locator, path, query = {}) {
|
|
819
984
|
if (closed || refusal !== null)
|
|
820
|
-
return
|
|
985
|
+
return UNCERTAIN;
|
|
821
986
|
try {
|
|
822
987
|
await ensureAuthorization();
|
|
823
988
|
}
|
|
824
989
|
catch {
|
|
825
|
-
return
|
|
826
|
-
}
|
|
827
|
-
if (closed || authorization === undefined || refusal !== null) {
|
|
828
|
-
return { kind: 'transport-uncertainty' };
|
|
990
|
+
return UNCERTAIN;
|
|
829
991
|
}
|
|
992
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
993
|
+
return UNCERTAIN;
|
|
830
994
|
const transfer = ['transfer', 'history', locator, path];
|
|
831
995
|
if (query.revision !== undefined)
|
|
832
996
|
transfer.push(query.revision);
|
|
@@ -848,12 +1012,96 @@ export function composeRuntime(options) {
|
|
|
848
1012
|
}
|
|
849
1013
|
if (result.kind === 'revoked-authorization')
|
|
850
1014
|
await refuse('revoked-authorization');
|
|
851
|
-
return result;
|
|
1015
|
+
return historyAnswer(result);
|
|
852
1016
|
}
|
|
853
1017
|
catch {
|
|
854
1018
|
journal.record('outcome history transport-uncertainty');
|
|
855
|
-
return
|
|
1019
|
+
return UNCERTAIN;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
function queuedBlob(locator, path, localId) {
|
|
1023
|
+
for (const batch of replicas.get(locator)?.queue ?? []) {
|
|
1024
|
+
const change = batch.changes[queuedFile(batch, path, localId)];
|
|
1025
|
+
if (change?.kind === 'binary')
|
|
1026
|
+
return change.blob;
|
|
856
1027
|
}
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
async function readBinary(request) {
|
|
1031
|
+
if (closed || refusal !== null)
|
|
1032
|
+
return UNCERTAIN;
|
|
1033
|
+
try {
|
|
1034
|
+
await ensureAuthorization();
|
|
1035
|
+
}
|
|
1036
|
+
catch {
|
|
1037
|
+
return UNCERTAIN;
|
|
1038
|
+
}
|
|
1039
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
1040
|
+
return UNCERTAIN;
|
|
1041
|
+
journal.record(request.kind === 'path'
|
|
1042
|
+
? `transfer binary ${request.locator} ${request.path}`
|
|
1043
|
+
: `transfer binary ${request.fileRef}`);
|
|
1044
|
+
try {
|
|
1045
|
+
const result = await options.adapter.binary(authorization, request);
|
|
1046
|
+
journal.record(`outcome binary ${result.kind}`);
|
|
1047
|
+
if (result.kind === 'scoped-refusal') {
|
|
1048
|
+
const room = replicas.get(result.locator)?.confirmed.record;
|
|
1049
|
+
if (room !== undefined)
|
|
1050
|
+
await scoped(result, room, await openLocal());
|
|
1051
|
+
}
|
|
1052
|
+
if (result.kind === 'revoked-authorization')
|
|
1053
|
+
await refuse('revoked-authorization');
|
|
1054
|
+
return binaryAnswer(result);
|
|
1055
|
+
}
|
|
1056
|
+
catch {
|
|
1057
|
+
journal.record('outcome binary transport-uncertainty');
|
|
1058
|
+
return UNCERTAIN;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
async function readBinaryPath(locator, path, revision) {
|
|
1062
|
+
await ready;
|
|
1063
|
+
if (revision !== undefined)
|
|
1064
|
+
return readBinary({ kind: 'path', locator, path, revision });
|
|
1065
|
+
const visible = replicas.get(locator)?.visible;
|
|
1066
|
+
const held = visible?.binaries[path];
|
|
1067
|
+
if (visible === undefined || held === undefined)
|
|
1068
|
+
return absentBinary(path);
|
|
1069
|
+
if (held.state === 'pending') {
|
|
1070
|
+
const blob = queuedBlob(locator, path, held.localId);
|
|
1071
|
+
return blob === null ? absentBinary(path) : { kind: 'binary', blob };
|
|
1072
|
+
}
|
|
1073
|
+
const record = binaryRecord(path, held, visible.revisions[path]);
|
|
1074
|
+
if (record.state !== 'accepted')
|
|
1075
|
+
return absentBinary(path);
|
|
1076
|
+
const replica = replicas.get(locator);
|
|
1077
|
+
const queued = (replica?.queue ?? []).flatMap((batch) => batch.changes);
|
|
1078
|
+
const holds = (at) => {
|
|
1079
|
+
const kept = replica?.confirmed.record.binaries[at];
|
|
1080
|
+
return (kept?.state === 'accepted' &&
|
|
1081
|
+
kept.fileRef === record.fileRef &&
|
|
1082
|
+
replica?.confirmed.record.revisions[at] === record.revision);
|
|
1083
|
+
};
|
|
1084
|
+
let confirmed = path;
|
|
1085
|
+
for (let index = queued.length - 1; index >= 0 && !holds(confirmed); index -= 1) {
|
|
1086
|
+
const change = queued[index];
|
|
1087
|
+
if (change.kind === 'move' && change.to === confirmed)
|
|
1088
|
+
confirmed = change.path;
|
|
1089
|
+
}
|
|
1090
|
+
return readBinary({
|
|
1091
|
+
kind: 'path',
|
|
1092
|
+
locator,
|
|
1093
|
+
path: confirmed,
|
|
1094
|
+
revision: record.revision,
|
|
1095
|
+
fileRef: record.fileRef,
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
async function readBinaryReference(reference) {
|
|
1099
|
+
await ready;
|
|
1100
|
+
return readBinary({
|
|
1101
|
+
kind: 'reference',
|
|
1102
|
+
fileRef: reference.fileRef,
|
|
1103
|
+
referrer: reference.referrer,
|
|
1104
|
+
});
|
|
857
1105
|
}
|
|
858
1106
|
function declaredAgents() {
|
|
859
1107
|
for (const capability of capabilities) {
|
|
@@ -874,18 +1122,17 @@ export function composeRuntime(options) {
|
|
|
874
1122
|
async function invokeAction(name, input) {
|
|
875
1123
|
await ready;
|
|
876
1124
|
if (closed || refusal !== null)
|
|
877
|
-
return
|
|
1125
|
+
return UNCERTAIN;
|
|
878
1126
|
if (declaredAction(name) === undefined)
|
|
879
|
-
return
|
|
1127
|
+
return undeclared(name);
|
|
880
1128
|
try {
|
|
881
1129
|
await ensureAuthorization();
|
|
882
1130
|
}
|
|
883
1131
|
catch {
|
|
884
|
-
return
|
|
885
|
-
}
|
|
886
|
-
if (closed || authorization === undefined || refusal !== null) {
|
|
887
|
-
return { kind: 'transport-uncertainty' };
|
|
1132
|
+
return UNCERTAIN;
|
|
888
1133
|
}
|
|
1134
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
1135
|
+
return UNCERTAIN;
|
|
889
1136
|
journal.record(`transfer action ${name}`);
|
|
890
1137
|
try {
|
|
891
1138
|
const result = await options.adapter.action(authorization, { name, input });
|
|
@@ -899,11 +1146,11 @@ export function composeRuntime(options) {
|
|
|
899
1146
|
journal.record(`outcome action ${result.kind}`);
|
|
900
1147
|
if (result.kind === 'rejected')
|
|
901
1148
|
journal.record(`refusal rejected ${result.code}`);
|
|
902
|
-
return result;
|
|
1149
|
+
return calledResult(result);
|
|
903
1150
|
}
|
|
904
1151
|
catch {
|
|
905
1152
|
journal.record('outcome action transport-uncertainty');
|
|
906
|
-
return
|
|
1153
|
+
return UNCERTAIN;
|
|
907
1154
|
}
|
|
908
1155
|
}
|
|
909
1156
|
function continuing() {
|
|
@@ -934,14 +1181,33 @@ export function composeRuntime(options) {
|
|
|
934
1181
|
throw error;
|
|
935
1182
|
}
|
|
936
1183
|
}
|
|
937
|
-
|
|
1184
|
+
function staging(local) {
|
|
1185
|
+
return {
|
|
1186
|
+
held: (locator) => transfers.get(locator) ?? null,
|
|
1187
|
+
staged: (record) => local.staged(record),
|
|
1188
|
+
stage: (record, page) => exclusive(async () => {
|
|
1189
|
+
if (!continuing())
|
|
1190
|
+
return false;
|
|
1191
|
+
await local.stage(record, page);
|
|
1192
|
+
transfers.set(record.locator, record);
|
|
1193
|
+
return true;
|
|
1194
|
+
}),
|
|
1195
|
+
abandon: (locator) => exclusive(async () => {
|
|
1196
|
+
if (!continuing())
|
|
1197
|
+
return;
|
|
1198
|
+
await local.abandon(locator);
|
|
1199
|
+
transfers.delete(locator);
|
|
1200
|
+
}),
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
async function pullBootstrap(authorized, room, local) {
|
|
938
1204
|
if (!continuing())
|
|
939
1205
|
return unconfirmed;
|
|
940
1206
|
try {
|
|
941
1207
|
const result = await bootstrapRoom(options.adapter, authorized, room, {
|
|
942
1208
|
collection: modelOf(room.locator),
|
|
943
1209
|
selection: modelOf(room.locator, 'typed-selection'),
|
|
944
|
-
}, (models, held) => remodel(room.locator, models, held), continuing);
|
|
1210
|
+
}, (models, held) => remodel(room.locator, models, held), continuing, staging(local));
|
|
945
1211
|
const detail = result.kind === 'staged' ? result.confirmed.record.roomVersion : result.kind;
|
|
946
1212
|
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
947
1213
|
return result;
|
|
@@ -969,7 +1235,7 @@ export function composeRuntime(options) {
|
|
|
969
1235
|
return null;
|
|
970
1236
|
}
|
|
971
1237
|
if (refreshed.kind === 'advanced') {
|
|
972
|
-
if (!(await publish(local, room, refreshed.confirmed, held.confirmed))) {
|
|
1238
|
+
if (!(await publish(local, room, refreshed.confirmed, { base: held.confirmed }))) {
|
|
973
1239
|
const now = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
974
1240
|
const reached = now !== undefined &&
|
|
975
1241
|
options.adapter.reached(room.locator, now, refreshed.confirmed.record.roomVersion);
|
|
@@ -981,12 +1247,12 @@ export function composeRuntime(options) {
|
|
|
981
1247
|
if (refreshed.kind !== 'beyond-horizon')
|
|
982
1248
|
return refreshed;
|
|
983
1249
|
}
|
|
984
|
-
const bootstrapped = await pullBootstrap(authorized, room);
|
|
1250
|
+
const bootstrapped = await pullBootstrap(authorized, room, local);
|
|
985
1251
|
if (!continuing())
|
|
986
1252
|
return unconfirmed;
|
|
987
1253
|
if (bootstrapped.kind !== 'staged')
|
|
988
1254
|
return bootstrapped;
|
|
989
|
-
await publish(local, room, bootstrapped.confirmed);
|
|
1255
|
+
await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored });
|
|
990
1256
|
return null;
|
|
991
1257
|
}
|
|
992
1258
|
async function withhold(withheld, room, local, progress) {
|
|
@@ -1018,6 +1284,7 @@ export function composeRuntime(options) {
|
|
|
1018
1284
|
function refusedSource(room, { refusal }) {
|
|
1019
1285
|
sourceRefusals.set(room.locator, {
|
|
1020
1286
|
code: refusal.rule,
|
|
1287
|
+
retryable: false,
|
|
1021
1288
|
fields: {
|
|
1022
1289
|
file: refusal.file,
|
|
1023
1290
|
...(refusal.location === undefined ? {} : { location: refusal.location }),
|
|
@@ -1032,8 +1299,16 @@ export function composeRuntime(options) {
|
|
|
1032
1299
|
}
|
|
1033
1300
|
function leaving(batch, result) {
|
|
1034
1301
|
if (result.kind === 'expired')
|
|
1035
|
-
return { batch, returned:
|
|
1036
|
-
return {
|
|
1302
|
+
return { batch, returned: returnedBy('expired') };
|
|
1303
|
+
return {
|
|
1304
|
+
batch,
|
|
1305
|
+
returned: {
|
|
1306
|
+
kind: result.kind,
|
|
1307
|
+
code: result.code,
|
|
1308
|
+
fields: result.fields,
|
|
1309
|
+
retryable: result.retryable,
|
|
1310
|
+
},
|
|
1311
|
+
};
|
|
1037
1312
|
}
|
|
1038
1313
|
function logAccepted(locator, submitted, accepted) {
|
|
1039
1314
|
journal.record(`outcome ${locator} ${submitted.id} accepted ${accepted.roomVersion}` +
|
|
@@ -1051,7 +1326,7 @@ export function composeRuntime(options) {
|
|
|
1051
1326
|
logAccepted(room.locator, submitted, accepted);
|
|
1052
1327
|
const applied = options.adapter.reached(room.locator, held, accepted.roomVersion)
|
|
1053
1328
|
? { kind: 'staged', confirmed: current.confirmed }
|
|
1054
|
-
: applyReturned(current.confirmed,
|
|
1329
|
+
: applyReturned(current.confirmed, submitted, accepted, options.adapter.follows(room.locator, held, accepted.roomVersion)
|
|
1055
1330
|
? accepted.roomVersion
|
|
1056
1331
|
: held, accepted.roomVersion);
|
|
1057
1332
|
if (applied.kind !== 'staged') {
|
|
@@ -1259,7 +1534,7 @@ export function composeRuntime(options) {
|
|
|
1259
1534
|
...(attached === 'collection' ? { model: next } : { selectionModel: next }),
|
|
1260
1535
|
};
|
|
1261
1536
|
const reclassified = sameMembership(declared, held.confirmed.record)
|
|
1262
|
-
?
|
|
1537
|
+
? { kind: 'staged', confirmed: confirmedOf(declared, held.confirmed) }
|
|
1263
1538
|
: readReclassified(declared, held.confirmed);
|
|
1264
1539
|
const record = reclassified.kind === 'staged'
|
|
1265
1540
|
? declared
|
|
@@ -1267,12 +1542,10 @@ export function composeRuntime(options) {
|
|
|
1267
1542
|
...declared,
|
|
1268
1543
|
typedSelection: held.confirmed.record.typedSelection,
|
|
1269
1544
|
};
|
|
1270
|
-
const read = reclassified.kind === 'deferred'
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
queue: held.queue,
|
|
1275
|
-
});
|
|
1545
|
+
const read = reclassified.kind === 'deferred'
|
|
1546
|
+
? { kind: 'staged', confirmed: confirmedOf(record, held.confirmed) }
|
|
1547
|
+
: reclassified;
|
|
1548
|
+
await local.write({ next: record, previous: held.confirmed.record });
|
|
1276
1549
|
if (read.kind === 'staged') {
|
|
1277
1550
|
replicas.set(room.locator, replicaOf(read.confirmed, held.queue, held));
|
|
1278
1551
|
if (declarationRefusals.delete(room.locator)) {
|
|
@@ -1299,7 +1572,11 @@ export function composeRuntime(options) {
|
|
|
1299
1572
|
});
|
|
1300
1573
|
}
|
|
1301
1574
|
async function scoped(result, room, local) {
|
|
1302
|
-
scopedRefusals.set(room.locator, {
|
|
1575
|
+
scopedRefusals.set(room.locator, {
|
|
1576
|
+
code: result.code,
|
|
1577
|
+
fields: result.fields,
|
|
1578
|
+
retryable: result.retryable,
|
|
1579
|
+
});
|
|
1303
1580
|
let scopedAway = room;
|
|
1304
1581
|
if (result.scope === 'model')
|
|
1305
1582
|
await retainModel(local, room, 'collection', {
|
|
@@ -1336,7 +1613,6 @@ export function composeRuntime(options) {
|
|
|
1336
1613
|
roomVersion: record.roomVersion,
|
|
1337
1614
|
documents: record.documents,
|
|
1338
1615
|
revisions: record.revisions,
|
|
1339
|
-
attachments: record.attachments,
|
|
1340
1616
|
},
|
|
1341
1617
|
];
|
|
1342
1618
|
});
|
|
@@ -1416,14 +1692,14 @@ export function composeRuntime(options) {
|
|
|
1416
1692
|
async function reload(room, local, progress) {
|
|
1417
1693
|
if (discoveryPaused || authorization === undefined)
|
|
1418
1694
|
return;
|
|
1419
|
-
const bootstrapped = await pullBootstrap(authorization, room);
|
|
1695
|
+
const bootstrapped = await pullBootstrap(authorization, room, local);
|
|
1420
1696
|
if (!continuing())
|
|
1421
1697
|
return;
|
|
1422
1698
|
if (bootstrapped.kind !== 'staged') {
|
|
1423
1699
|
await withhold(bootstrapped, room, local, progress);
|
|
1424
1700
|
return;
|
|
1425
1701
|
}
|
|
1426
|
-
await publish(local, room, bootstrapped.confirmed);
|
|
1702
|
+
await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored });
|
|
1427
1703
|
await caughtUp(room, local, progress);
|
|
1428
1704
|
}
|
|
1429
1705
|
async function discoverRooms(local, progress, attempt) {
|
|
@@ -1458,7 +1734,11 @@ export function composeRuntime(options) {
|
|
|
1458
1734
|
}
|
|
1459
1735
|
if (result.kind === 'contract-unavailable') {
|
|
1460
1736
|
journal.record(`connection contract unavailable ${result.code}`);
|
|
1461
|
-
await refuse('missing-required-guarantee', {
|
|
1737
|
+
await refuse('missing-required-guarantee', {
|
|
1738
|
+
code: result.code,
|
|
1739
|
+
fields: result.fields,
|
|
1740
|
+
retryable: result.retryable,
|
|
1741
|
+
});
|
|
1462
1742
|
notifyReady();
|
|
1463
1743
|
return;
|
|
1464
1744
|
}
|
|
@@ -1534,6 +1814,8 @@ export function composeRuntime(options) {
|
|
|
1534
1814
|
}
|
|
1535
1815
|
const progress = { complete: true, reachable: true };
|
|
1536
1816
|
underway = attempt === 'opened' ? progress : null;
|
|
1817
|
+
if (attempt === 'opened')
|
|
1818
|
+
streamed = progress;
|
|
1537
1819
|
complete = false;
|
|
1538
1820
|
recorded = false;
|
|
1539
1821
|
moveTo(transferring(status));
|
|
@@ -1574,9 +1856,14 @@ export function composeRuntime(options) {
|
|
|
1574
1856
|
void refuse('revoked-authorization');
|
|
1575
1857
|
return;
|
|
1576
1858
|
}
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1859
|
+
complete = false;
|
|
1860
|
+
if (streamed !== null)
|
|
1861
|
+
streamed.complete = false;
|
|
1862
|
+
moveTo({
|
|
1863
|
+
...status,
|
|
1864
|
+
state: status.state === 'synced' ? 'syncing' : status.state,
|
|
1865
|
+
providerUnreachable: status.providerUnreachable || !opened,
|
|
1866
|
+
});
|
|
1580
1867
|
}
|
|
1581
1868
|
function applyOpening(expired) {
|
|
1582
1869
|
return task(async () => {
|
|
@@ -1631,6 +1918,8 @@ export function composeRuntime(options) {
|
|
|
1631
1918
|
}
|
|
1632
1919
|
}
|
|
1633
1920
|
function submitLater(locator) {
|
|
1921
|
+
if (localSaves)
|
|
1922
|
+
return;
|
|
1634
1923
|
const running = submitting.get(locator);
|
|
1635
1924
|
if (running !== undefined) {
|
|
1636
1925
|
running.again = true;
|
|
@@ -1710,7 +1999,7 @@ export function composeRuntime(options) {
|
|
|
1710
1999
|
}
|
|
1711
2000
|
}
|
|
1712
2001
|
moveTo({
|
|
1713
|
-
state: pending() ? 'pending' : synced ? 'synced' : 'syncing',
|
|
2002
|
+
state: pending() ? 'pending' : synced && complete ? 'synced' : 'syncing',
|
|
1714
2003
|
lastCompleted,
|
|
1715
2004
|
providerUnreachable: !progress.reachable,
|
|
1716
2005
|
...attention(),
|
|
@@ -1819,20 +2108,15 @@ export function composeRuntime(options) {
|
|
|
1819
2108
|
show();
|
|
1820
2109
|
return;
|
|
1821
2110
|
}
|
|
1822
|
-
const
|
|
1823
|
-
|
|
1824
|
-
moveTo(transferring(resting));
|
|
2111
|
+
const progress = { complete: false, reachable: !status.providerUnreachable };
|
|
2112
|
+
moveTo(transferring(status));
|
|
1825
2113
|
try {
|
|
1826
2114
|
await conversations.catchUp(progress);
|
|
1827
2115
|
}
|
|
1828
2116
|
catch {
|
|
1829
2117
|
progress.reachable = false;
|
|
1830
2118
|
}
|
|
1831
|
-
|
|
1832
|
-
if (!closed) {
|
|
1833
|
-
moveTo(settled(resting, progress, resting.lastCompleted, pending(), attention()));
|
|
1834
|
-
}
|
|
1835
|
-
}
|
|
2119
|
+
await rest(progress);
|
|
1836
2120
|
}
|
|
1837
2121
|
function close() {
|
|
1838
2122
|
if (closed)
|
|
@@ -1886,13 +2170,16 @@ export function composeRuntime(options) {
|
|
|
1886
2170
|
return planOf(input, roomFacts(reach), planReach(reach));
|
|
1887
2171
|
}
|
|
1888
2172
|
function watchPlan(plan, deliver) {
|
|
2173
|
+
materialize(reachedRooms(plan));
|
|
1889
2174
|
return readSide.watch(plan, identityOf(plan), deliver);
|
|
1890
2175
|
}
|
|
1891
2176
|
async function searched(input, reach) {
|
|
1892
2177
|
const plan = searchPlanOf(input);
|
|
1893
2178
|
const selected = plan.documents === undefined && reach === null
|
|
1894
2179
|
? null
|
|
1895
|
-
: (await
|
|
2180
|
+
: (await rowsQuery(filterOf(plan.documents ?? {}, roomFacts(reach), planReach(reach)))).documents;
|
|
2181
|
+
if (selected === null)
|
|
2182
|
+
materialize(null);
|
|
1896
2183
|
return searchIndex.search(reach === null || reach.conversations ? plan : { ...plan, conversations: [] }, selected);
|
|
1897
2184
|
}
|
|
1898
2185
|
const watch = (topic, input, deliver) => {
|
|
@@ -1918,6 +2205,8 @@ export function composeRuntime(options) {
|
|
|
1918
2205
|
}
|
|
1919
2206
|
if (topic === 'conversations')
|
|
1920
2207
|
return conversations.watchList(deliver);
|
|
2208
|
+
if (topic === 'agents')
|
|
2209
|
+
return conversations.watchAgents(deliver);
|
|
1921
2210
|
if (topic === 'presentation') {
|
|
1922
2211
|
const listener = {
|
|
1923
2212
|
app: input[0],
|
|
@@ -1996,7 +2285,7 @@ export function composeRuntime(options) {
|
|
|
1996
2285
|
rooms() {
|
|
1997
2286
|
return listed();
|
|
1998
2287
|
},
|
|
1999
|
-
query: ((input) =>
|
|
2288
|
+
query: ((input) => rowsQuery(planned(input, null))),
|
|
2000
2289
|
search: (input) => searched(input, null),
|
|
2001
2290
|
types(locator) {
|
|
2002
2291
|
return {
|
|
@@ -2025,20 +2314,20 @@ export function composeRuntime(options) {
|
|
|
2025
2314
|
return exclusive(async () => {
|
|
2026
2315
|
const parts = readParts(input);
|
|
2027
2316
|
if (path === '' || normalize(path) !== path)
|
|
2028
|
-
throw
|
|
2317
|
+
throw runtimeError('room-draft-malformed');
|
|
2029
2318
|
const local = await openLocal();
|
|
2030
2319
|
const current = roomDrafts.get(room)?.get(path);
|
|
2031
2320
|
if ((current?.revision ?? null) !== revision) {
|
|
2032
|
-
return
|
|
2033
|
-
outcome: 'stale',
|
|
2034
|
-
current: current === undefined ? null : roomDraftOf(current),
|
|
2035
|
-
};
|
|
2321
|
+
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
2036
2322
|
}
|
|
2323
|
+
const file = parts.file === undefined
|
|
2324
|
+
? undefined
|
|
2325
|
+
: await heldFile(parts.file.blob, binaryBase(replicas.get(room)?.visible, path));
|
|
2037
2326
|
const record = {
|
|
2038
2327
|
locator: room,
|
|
2039
2328
|
path,
|
|
2040
2329
|
revision: roomDraftRevision(),
|
|
2041
|
-
parts: held(current?.parts, parts),
|
|
2330
|
+
parts: held(current?.parts, parts, file),
|
|
2042
2331
|
returned: current?.returned ?? null,
|
|
2043
2332
|
};
|
|
2044
2333
|
await local.keepRoomDraft(record);
|
|
@@ -2052,10 +2341,7 @@ export function composeRuntime(options) {
|
|
|
2052
2341
|
const local = await openLocal();
|
|
2053
2342
|
const current = roomDrafts.get(room)?.get(path);
|
|
2054
2343
|
if (current === undefined || current.revision !== revision) {
|
|
2055
|
-
return
|
|
2056
|
-
outcome: 'stale',
|
|
2057
|
-
current: current === undefined ? null : roomDraftOf(current),
|
|
2058
|
-
};
|
|
2344
|
+
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
2059
2345
|
}
|
|
2060
2346
|
await local.discardRoomDraft(room, path);
|
|
2061
2347
|
forgetRoomDraft(room, path);
|
|
@@ -2090,6 +2376,10 @@ export function composeRuntime(options) {
|
|
|
2090
2376
|
history(locator, path, query) {
|
|
2091
2377
|
return readHistory(locator, path, query);
|
|
2092
2378
|
},
|
|
2379
|
+
binaryFile: {
|
|
2380
|
+
read: readBinaryPath,
|
|
2381
|
+
readReference: readBinaryReference,
|
|
2382
|
+
},
|
|
2093
2383
|
call(name, input) {
|
|
2094
2384
|
return invokeAction(name, input);
|
|
2095
2385
|
},
|
|
@@ -2122,8 +2412,9 @@ export function composeRuntime(options) {
|
|
|
2122
2412
|
const reachable = {
|
|
2123
2413
|
md,
|
|
2124
2414
|
watch,
|
|
2415
|
+
referrerScope: (referrer) => options.adapter.referrerScope(referrer),
|
|
2125
2416
|
plan: planned,
|
|
2126
|
-
query: (plan) =>
|
|
2417
|
+
query: (plan) => rowsQuery(plan),
|
|
2127
2418
|
watchPlan,
|
|
2128
2419
|
search: searched,
|
|
2129
2420
|
};
|