@agentshouse/mdruntime 0.1.0-cd50a4607cb7d → 0.1.0-cdb8840578a20
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 +42 -38
- package/dist/acquisition.d.ts +14 -0
- package/dist/acquisition.d.ts.map +1 -0
- package/dist/acquisition.js +24 -0
- package/dist/adapter.d.ts +9 -7
- package/dist/adapter.d.ts.map +1 -1
- package/dist/chat/kit.d.ts +2 -1
- package/dist/chat/kit.d.ts.map +1 -1
- package/dist/chat/kit.js +2 -2
- package/dist/chat/list.d.ts +2 -1
- package/dist/chat/list.d.ts.map +1 -1
- package/dist/chat/list.js +4 -3
- package/dist/chat/messages.d.ts +1 -0
- package/dist/chat/messages.d.ts.map +1 -1
- package/dist/chat/messages.js +4 -0
- package/dist/chat/provider.d.ts +1 -1
- package/dist/chat/provider.d.ts.map +1 -1
- package/dist/chat/provider.js +26 -17
- package/dist/chat/ready.d.ts +1 -0
- package/dist/chat/ready.d.ts.map +1 -1
- package/dist/chat/ready.js +2 -2
- package/dist/client.d.ts +5 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -0
- package/dist/connection.d.ts +13 -2
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +424 -36
- package/dist/conversation.d.ts +2 -0
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +75 -12
- package/dist/durability.d.ts +7 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/host.d.ts +10 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +35 -6
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/memory.d.ts +2 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +6 -1
- package/dist/model.d.ts +3 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +1 -1
- package/dist/query.d.ts +2 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/reach.d.ts +6 -2
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +14 -2
- package/dist/react.d.ts +2 -0
- package/dist/react.d.ts.map +1 -1
- package/dist/readiness.d.ts +53 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +33 -0
- package/dist/room-draft.d.ts +1 -1
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/search.d.ts +6 -5
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +6 -8
- package/dist/store.d.ts +9 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +35 -11
- package/dist/wire.d.ts +136 -32
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +48 -31
- package/package.json +1 -1
package/dist/connection.js
CHANGED
|
@@ -21,6 +21,8 @@ import { applyReturned, compositionOf, confirmedOf, landed, readReclassified, re
|
|
|
21
21
|
import { binaryBase, changedParts, changesOf, held, heldFile, joinedDraft, readParts, returnedBy, returnedInto, staleWrite, underneath, withdrawnDrafts, } from './room-draft.js';
|
|
22
22
|
import { same, saving, STARTING, transferring, } from './status.js';
|
|
23
23
|
import { detectStorageLoss, nextGeneration } from './durability.js';
|
|
24
|
+
import { demanded, narrower, WHOLE_DEMAND, wider } from './acquisition.js';
|
|
25
|
+
import { readinessOf, SETTLED_ROOMS, } from './readiness.js';
|
|
24
26
|
import { FencedMap } from './fence.js';
|
|
25
27
|
import { tally } from './shared.js';
|
|
26
28
|
import { inChunks, ROW_WEIGHT, yielded } from './turn.js';
|
|
@@ -166,8 +168,18 @@ export function composeRuntime(options) {
|
|
|
166
168
|
const memberListeners = new Set();
|
|
167
169
|
const prepared = new Set();
|
|
168
170
|
const projecting = new Set();
|
|
169
|
-
const
|
|
171
|
+
const unpreparedDemand = new Set();
|
|
172
|
+
const unindexable = new Set();
|
|
170
173
|
let preparing = false;
|
|
174
|
+
let demand = WHOLE_DEMAND;
|
|
175
|
+
let narrowing = null;
|
|
176
|
+
let targets = {};
|
|
177
|
+
let persistence = options.shell.persistence;
|
|
178
|
+
let capacity = null;
|
|
179
|
+
const paused = new Set();
|
|
180
|
+
let rechecking = null;
|
|
181
|
+
const readinessWatches = new Set();
|
|
182
|
+
let readinessQueued = false;
|
|
171
183
|
const catching = new Set();
|
|
172
184
|
const transfers = new FencedMap(isFenced);
|
|
173
185
|
const conversations = createConversationStore({
|
|
@@ -325,15 +337,18 @@ export function composeRuntime(options) {
|
|
|
325
337
|
}
|
|
326
338
|
function emitRooms() {
|
|
327
339
|
readSide.recover();
|
|
340
|
+
queueReadiness();
|
|
328
341
|
const value = listed();
|
|
329
342
|
for (const listener of roomListeners)
|
|
330
343
|
listener(value);
|
|
331
344
|
}
|
|
332
345
|
function emitRefusal() {
|
|
346
|
+
queueReadiness();
|
|
333
347
|
for (const listener of refusalListeners)
|
|
334
348
|
listener(refusal);
|
|
335
349
|
}
|
|
336
350
|
function emitStorageLoss() {
|
|
351
|
+
queueReadiness();
|
|
337
352
|
for (const listener of storageLossListeners)
|
|
338
353
|
listener(storageLoss);
|
|
339
354
|
}
|
|
@@ -350,7 +365,8 @@ export function composeRuntime(options) {
|
|
|
350
365
|
function show() {
|
|
351
366
|
if (closed)
|
|
352
367
|
return;
|
|
353
|
-
|
|
368
|
+
queueReadiness();
|
|
369
|
+
const next = status.state === 'synced' && demand.conversations && !conversations.current()
|
|
354
370
|
? { ...status, state: 'syncing' }
|
|
355
371
|
: status;
|
|
356
372
|
if (same(shown, next))
|
|
@@ -427,13 +443,19 @@ export function composeRuntime(options) {
|
|
|
427
443
|
}
|
|
428
444
|
conversations.changed(mergeChangeSets(entered));
|
|
429
445
|
readSide.recover();
|
|
446
|
+
queueReadiness();
|
|
430
447
|
}
|
|
431
448
|
function unprepared() {
|
|
432
|
-
return [...
|
|
449
|
+
return [...unpreparedDemand, ...replicas.keys()].find((locator) => !prepared.has(locator) && !unindexable.has(locator));
|
|
450
|
+
}
|
|
451
|
+
function unbuilt(locator, error) {
|
|
452
|
+
unindexable.add(locator);
|
|
453
|
+
journal.record(`index failed ${locator} ${error instanceof Error ? error.name : 'unknown'}`);
|
|
454
|
+
queueReadiness();
|
|
433
455
|
}
|
|
434
456
|
function prepare(locators = []) {
|
|
435
457
|
for (const locator of locators)
|
|
436
|
-
|
|
458
|
+
unpreparedDemand.add(locator);
|
|
437
459
|
if (preparing)
|
|
438
460
|
return;
|
|
439
461
|
preparing = true;
|
|
@@ -443,8 +465,8 @@ export function composeRuntime(options) {
|
|
|
443
465
|
await yielded();
|
|
444
466
|
if (closed)
|
|
445
467
|
return;
|
|
446
|
-
|
|
447
|
-
await prepareReplica(waiting);
|
|
468
|
+
unpreparedDemand.delete(waiting);
|
|
469
|
+
await prepareReplica(waiting).catch((error) => unbuilt(waiting, error));
|
|
448
470
|
}
|
|
449
471
|
}
|
|
450
472
|
finally {
|
|
@@ -502,6 +524,292 @@ export function composeRuntime(options) {
|
|
|
502
524
|
if (waiting.length > 0)
|
|
503
525
|
prepare(waiting.map(({ room }) => room));
|
|
504
526
|
}
|
|
527
|
+
function acquires(room) {
|
|
528
|
+
return (demanded(demand, room) ||
|
|
529
|
+
(replicas.get(room.locator)?.queue.length ?? 0) > 0 ||
|
|
530
|
+
queuesWithoutReplica.has(room.locator));
|
|
531
|
+
}
|
|
532
|
+
function acquiredLocators() {
|
|
533
|
+
return [...discovered.values()]
|
|
534
|
+
.filter(acquires)
|
|
535
|
+
.map((room) => room.locator)
|
|
536
|
+
.sort();
|
|
537
|
+
}
|
|
538
|
+
function heldDemand(acquired) {
|
|
539
|
+
if (demand.whole)
|
|
540
|
+
return null;
|
|
541
|
+
return { rooms: acquired.map((room) => room.locator), conversations: demand.conversations };
|
|
542
|
+
}
|
|
543
|
+
function applyDemand(next) {
|
|
544
|
+
const before = acquiredLocators();
|
|
545
|
+
const conversed = demand.conversations;
|
|
546
|
+
demand = next;
|
|
547
|
+
const after = acquiredLocators();
|
|
548
|
+
queueReadiness();
|
|
549
|
+
if (!conversed && next.conversations)
|
|
550
|
+
scheduleCurrency();
|
|
551
|
+
if (JSON.stringify(before) === JSON.stringify(after) && conversed === next.conversations)
|
|
552
|
+
return;
|
|
553
|
+
journal.record(`acquisition ${next.whole ? 'whole' : after.length}`);
|
|
554
|
+
if (options.adapter.roomStream === undefined)
|
|
555
|
+
void sync('unstreamed');
|
|
556
|
+
else if (socketOpen)
|
|
557
|
+
options.adapter.roomStream.reopen();
|
|
558
|
+
}
|
|
559
|
+
function acquire(next) {
|
|
560
|
+
if (closed)
|
|
561
|
+
return;
|
|
562
|
+
if (narrowing !== null)
|
|
563
|
+
clearTimeout(narrowing);
|
|
564
|
+
narrowing = null;
|
|
565
|
+
const widening = wider(next, demand);
|
|
566
|
+
if (!widening && !narrower(next, demand))
|
|
567
|
+
return;
|
|
568
|
+
if (widening || !roomsListed) {
|
|
569
|
+
applyDemand(next);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
// A narrower demand waits five seconds, so a tab that reloads returns before the stream reopens twice.
|
|
573
|
+
narrowing = setTimeout(() => {
|
|
574
|
+
narrowing = null;
|
|
575
|
+
applyDemand(next);
|
|
576
|
+
}, 5_000);
|
|
577
|
+
}
|
|
578
|
+
function requiredRooms(reach) {
|
|
579
|
+
if (reach === null)
|
|
580
|
+
return { listed: roomsListed, rooms: [...rooms.keys()].sort() };
|
|
581
|
+
const named = [...new Set(reach.rooms)].sort();
|
|
582
|
+
if (roomsListed)
|
|
583
|
+
return { listed: true, rooms: named.filter((room) => rooms.has(room)) };
|
|
584
|
+
return { listed: named.every((room) => replicas.has(room)), rooms: named };
|
|
585
|
+
}
|
|
586
|
+
function roomState(locator, search) {
|
|
587
|
+
if (sourceRefusals.has(locator))
|
|
588
|
+
return 'refused';
|
|
589
|
+
const listedRoom = discovered.get(locator);
|
|
590
|
+
if (roomsListed &&
|
|
591
|
+
(listedRoom === undefined ||
|
|
592
|
+
listedRoom.replica.admission !== 'admitted' ||
|
|
593
|
+
unreadable.has(locator)))
|
|
594
|
+
return 'unadmitted';
|
|
595
|
+
if (!replicas.has(locator))
|
|
596
|
+
return 'acquiring';
|
|
597
|
+
if (!prepared.has(locator))
|
|
598
|
+
return 'preparing';
|
|
599
|
+
if (search && !searchIndex.rooms().has(locator))
|
|
600
|
+
return 'indexing';
|
|
601
|
+
return 'complete';
|
|
602
|
+
}
|
|
603
|
+
function conversationCoverage(reach, search) {
|
|
604
|
+
const declared = reach === null || reach.conversations;
|
|
605
|
+
if (!declared || options.adapter.conversations === undefined || declaredAgents() === undefined)
|
|
606
|
+
return 'undeclared';
|
|
607
|
+
if (!conversations.known() || !conversations.current())
|
|
608
|
+
return 'acquiring';
|
|
609
|
+
if (!search)
|
|
610
|
+
return 'complete';
|
|
611
|
+
const unindexed = conversations
|
|
612
|
+
.searchable()
|
|
613
|
+
.filter((one) => conversations.acquired(one) && !searchIndex.conversations().has(one));
|
|
614
|
+
if (unindexed.length === 0)
|
|
615
|
+
return 'complete';
|
|
616
|
+
index([], unindexed);
|
|
617
|
+
return 'indexing';
|
|
618
|
+
}
|
|
619
|
+
function connectionOf() {
|
|
620
|
+
if (socketOpen)
|
|
621
|
+
return 'connected';
|
|
622
|
+
if (status.providerUnreachable)
|
|
623
|
+
return 'unreachable';
|
|
624
|
+
return options.adapter.roomStream === undefined && status.lastCompleted !== null
|
|
625
|
+
? 'connected'
|
|
626
|
+
: 'connecting';
|
|
627
|
+
}
|
|
628
|
+
function refusedIn(locators) {
|
|
629
|
+
return locators.flatMap((room) => {
|
|
630
|
+
const held = sourceRefusals.get(room);
|
|
631
|
+
if (held === undefined)
|
|
632
|
+
return [];
|
|
633
|
+
const { file, location } = held.fields;
|
|
634
|
+
return [
|
|
635
|
+
{
|
|
636
|
+
room,
|
|
637
|
+
file: typeof file === 'string' ? file : '',
|
|
638
|
+
rule: held.code,
|
|
639
|
+
location: typeof location === 'string' ? location : null,
|
|
640
|
+
},
|
|
641
|
+
];
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
function readinessFor(port, reach) {
|
|
645
|
+
const required = requiredRooms(reach);
|
|
646
|
+
const target = targets[port.target];
|
|
647
|
+
const search = target?.search === true;
|
|
648
|
+
const coverage = required.rooms.map((room) => ({ room, state: roomState(room, search) }));
|
|
649
|
+
const building = (state) => coverage
|
|
650
|
+
.filter((room) => room.state === state && !unindexable.has(room.room))
|
|
651
|
+
.map(({ room }) => room);
|
|
652
|
+
const preparing = building('preparing');
|
|
653
|
+
if (preparing.length > 0)
|
|
654
|
+
prepare(preparing);
|
|
655
|
+
const indexing = building('indexing');
|
|
656
|
+
if (indexing.length > 0)
|
|
657
|
+
index(indexing, []);
|
|
658
|
+
return readinessOf({
|
|
659
|
+
code: port.code,
|
|
660
|
+
coverage: {
|
|
661
|
+
listed: required.listed,
|
|
662
|
+
rooms: coverage,
|
|
663
|
+
conversations: conversationCoverage(reach, search),
|
|
664
|
+
search: search ? 'required' : 'unused',
|
|
665
|
+
},
|
|
666
|
+
persistence,
|
|
667
|
+
connection: connectionOf(),
|
|
668
|
+
house: refusal !== null ? 'refused' : writesHeld() ? 'write-halted' : 'open',
|
|
669
|
+
capacity: capacity !== null &&
|
|
670
|
+
coverage.some(({ room, state }) => paused.has(room) || !SETTLED_ROOMS.has(state))
|
|
671
|
+
? capacity
|
|
672
|
+
: port.capacity,
|
|
673
|
+
refused: refusedIn(required.rooms),
|
|
674
|
+
lost: storageLoss !== null,
|
|
675
|
+
established: target?.established === true,
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
function keepTarget(name, change) {
|
|
679
|
+
const held = targets[name] ?? { established: false, search: false };
|
|
680
|
+
const next = { ...held, ...change };
|
|
681
|
+
if (next.established === held.established && next.search === held.search)
|
|
682
|
+
return;
|
|
683
|
+
targets = { ...targets, [name]: next };
|
|
684
|
+
queueReadiness();
|
|
685
|
+
void durably((local) => local.keepTargets(targets)).catch(() => {
|
|
686
|
+
journal.record(`target record failed ${name}`);
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
function deliverReadiness(watch) {
|
|
690
|
+
if (!readinessWatches.has(watch))
|
|
691
|
+
return;
|
|
692
|
+
const readiness = readinessFor(watch.port, watch.reach);
|
|
693
|
+
if (readiness.ready && targets[watch.port.target]?.established !== true) {
|
|
694
|
+
keepTarget(watch.port.target, { established: true });
|
|
695
|
+
}
|
|
696
|
+
const shown = JSON.stringify(readiness);
|
|
697
|
+
if (shown === watch.last)
|
|
698
|
+
return;
|
|
699
|
+
watch.last = shown;
|
|
700
|
+
watch.deliver(structuredClone(readiness));
|
|
701
|
+
}
|
|
702
|
+
function queueReadiness() {
|
|
703
|
+
if (readinessQueued || closed || readinessWatches.size === 0)
|
|
704
|
+
return;
|
|
705
|
+
readinessQueued = true;
|
|
706
|
+
queueMicrotask(() => {
|
|
707
|
+
readinessQueued = false;
|
|
708
|
+
if (closed)
|
|
709
|
+
return;
|
|
710
|
+
for (const watch of [...readinessWatches])
|
|
711
|
+
deliverReadiness(watch);
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
function watchReadiness(port, reach, deliver) {
|
|
715
|
+
const watch = { port, reach, deliver, last: null };
|
|
716
|
+
readinessWatches.add(watch);
|
|
717
|
+
void openLocal().then(() => deliverReadiness(watch), () => deliverReadiness(watch));
|
|
718
|
+
return () => {
|
|
719
|
+
readinessWatches.delete(watch);
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
function searching(target) {
|
|
723
|
+
if (targets[target]?.search !== true)
|
|
724
|
+
keepTarget(target, { search: true });
|
|
725
|
+
}
|
|
726
|
+
function layerBytes(layer) {
|
|
727
|
+
let bytes = 0;
|
|
728
|
+
for (const [path, text] of Object.entries(layer.documents))
|
|
729
|
+
bytes += (path.length + text.length) * 2;
|
|
730
|
+
return bytes;
|
|
731
|
+
}
|
|
732
|
+
function grownBy(next, previous) {
|
|
733
|
+
if (previous === undefined)
|
|
734
|
+
return layerBytes(next);
|
|
735
|
+
const documents = Object.fromEntries(Object.entries(next.documents).filter(([path, text]) => previous.documents[path] !== text));
|
|
736
|
+
return layerBytes({ ...next, documents });
|
|
737
|
+
}
|
|
738
|
+
function exceeded(error) {
|
|
739
|
+
return (typeof error === 'object' &&
|
|
740
|
+
error !== null &&
|
|
741
|
+
error.name === 'QuotaExceededError');
|
|
742
|
+
}
|
|
743
|
+
async function estimated() {
|
|
744
|
+
const estimate = options.shell.estimate === undefined ? null : await options.shell.estimate();
|
|
745
|
+
return estimate === null ? null : Math.max(0, estimate.quota - estimate.usage);
|
|
746
|
+
}
|
|
747
|
+
function pause(locator, cause) {
|
|
748
|
+
paused.add(locator);
|
|
749
|
+
capacity = cause;
|
|
750
|
+
journal.record(`capacity ${cause.cause} ${locator} ${cause.needed} ${cause.available ?? '-'}`);
|
|
751
|
+
queueReadiness();
|
|
752
|
+
if (rechecking !== null)
|
|
753
|
+
return;
|
|
754
|
+
// A paused preparation measures the browser's space again each minute, so space freed elsewhere resumes it without a retry.
|
|
755
|
+
rechecking = setInterval(() => {
|
|
756
|
+
void recheck();
|
|
757
|
+
}, 60_000);
|
|
758
|
+
}
|
|
759
|
+
async function recheck() {
|
|
760
|
+
const held = capacity;
|
|
761
|
+
if (held === null || closed)
|
|
762
|
+
return;
|
|
763
|
+
const available = await estimated().catch(() => null);
|
|
764
|
+
if (available === null || available < (held.needed ?? 0))
|
|
765
|
+
return;
|
|
766
|
+
if (held.cause === 'quota-exceeded' && (held.available === null || available <= held.available))
|
|
767
|
+
return;
|
|
768
|
+
resume();
|
|
769
|
+
}
|
|
770
|
+
function resume() {
|
|
771
|
+
if (rechecking !== null)
|
|
772
|
+
clearInterval(rechecking);
|
|
773
|
+
rechecking = null;
|
|
774
|
+
if (paused.size === 0 && capacity === null)
|
|
775
|
+
return;
|
|
776
|
+
paused.clear();
|
|
777
|
+
capacity = null;
|
|
778
|
+
journal.record('capacity resumed');
|
|
779
|
+
queueReadiness();
|
|
780
|
+
if (!closed && authorization !== undefined)
|
|
781
|
+
void sync('unstreamed');
|
|
782
|
+
}
|
|
783
|
+
async function reclaimed(local) {
|
|
784
|
+
const unreferenced = [...transfers.keys()].filter((locator) => {
|
|
785
|
+
const room = discovered.get(locator);
|
|
786
|
+
return room === undefined || !acquires(room);
|
|
787
|
+
});
|
|
788
|
+
for (const locator of unreferenced) {
|
|
789
|
+
await local.abandon(locator);
|
|
790
|
+
transfers.delete(locator);
|
|
791
|
+
}
|
|
792
|
+
if (unreferenced.length > 0)
|
|
793
|
+
journal.record(`capacity reclaimed ${unreferenced.length}`);
|
|
794
|
+
return unreferenced.length > 0;
|
|
795
|
+
}
|
|
796
|
+
async function admitted(local, locator, needed) {
|
|
797
|
+
if (paused.has(locator))
|
|
798
|
+
return false;
|
|
799
|
+
let available = await estimated().catch(() => null);
|
|
800
|
+
if (available === null || available >= needed)
|
|
801
|
+
return true;
|
|
802
|
+
if (await reclaimed(local))
|
|
803
|
+
available = await estimated().catch(() => null);
|
|
804
|
+
if (available === null || available >= needed)
|
|
805
|
+
return true;
|
|
806
|
+
pause(locator, { cause: 'insufficient-space', needed, available });
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
async function exceededOn(locator, bytes) {
|
|
810
|
+
const available = await estimated().catch(() => null);
|
|
811
|
+
pause(locator, { cause: 'quota-exceeded', needed: bytes, available });
|
|
812
|
+
}
|
|
505
813
|
async function rowsQuery(plan) {
|
|
506
814
|
const before = covered(plan);
|
|
507
815
|
awaitPreparation(before);
|
|
@@ -529,10 +837,12 @@ export function composeRuntime(options) {
|
|
|
529
837
|
}
|
|
530
838
|
}
|
|
531
839
|
function indexNext() {
|
|
532
|
-
const room = [...roomsToIndex].find((locator) => !searchIndex.rooms().has(locator));
|
|
840
|
+
const room = [...roomsToIndex].find((locator) => !searchIndex.rooms().has(locator) && !unindexable.has(locator));
|
|
533
841
|
if (room !== undefined) {
|
|
534
842
|
roomsToIndex.delete(room);
|
|
535
|
-
return () =>
|
|
843
|
+
return () => prepared.has(room)
|
|
844
|
+
? searchIndex.indexRoom(room).catch((error) => unbuilt(room, error))
|
|
845
|
+
: undefined;
|
|
536
846
|
}
|
|
537
847
|
const conversation = [...conversationsToIndex].find((one) => !searchIndex.conversations().has(one));
|
|
538
848
|
if (conversation === undefined)
|
|
@@ -553,6 +863,7 @@ export function composeRuntime(options) {
|
|
|
553
863
|
for (let unit = indexNext(); unit !== null && !closed; unit = indexNext()) {
|
|
554
864
|
await yielded();
|
|
555
865
|
await unit();
|
|
866
|
+
queueReadiness();
|
|
556
867
|
}
|
|
557
868
|
}
|
|
558
869
|
finally {
|
|
@@ -726,6 +1037,8 @@ export function composeRuntime(options) {
|
|
|
726
1037
|
unreadable.delete(locator);
|
|
727
1038
|
stale.delete(locator);
|
|
728
1039
|
repairable.delete(locator);
|
|
1040
|
+
unindexable.delete(locator);
|
|
1041
|
+
paused.delete(locator);
|
|
729
1042
|
scopedRefusals.delete(locator);
|
|
730
1043
|
sourceRefusals.delete(locator);
|
|
731
1044
|
declarationRefusals.delete(locator);
|
|
@@ -779,7 +1092,7 @@ export function composeRuntime(options) {
|
|
|
779
1092
|
return model;
|
|
780
1093
|
}
|
|
781
1094
|
async function hydrate(opened) {
|
|
782
|
-
const [records, queues, kept, presented, held, chatDrafts, lastCompleted, declared, named, known, staged,] = await Promise.all([
|
|
1095
|
+
const [records, queues, kept, presented, held, chatDrafts, lastCompleted, declared, named, known, staged, heldTargets,] = await Promise.all([
|
|
783
1096
|
opened.all(),
|
|
784
1097
|
opened.queues(),
|
|
785
1098
|
opened.roomDrafts(),
|
|
@@ -791,7 +1104,9 @@ export function composeRuntime(options) {
|
|
|
791
1104
|
opened.capabilities(),
|
|
792
1105
|
opened.member(),
|
|
793
1106
|
opened.transfers(),
|
|
1107
|
+
opened.targets(),
|
|
794
1108
|
]);
|
|
1109
|
+
targets = heldTargets;
|
|
795
1110
|
transfers.clear();
|
|
796
1111
|
for (const record of staged)
|
|
797
1112
|
transfers.set(record.locator, record);
|
|
@@ -1037,7 +1352,18 @@ export function composeRuntime(options) {
|
|
|
1037
1352
|
const accepted = pendings.get(batch.id)?.accepted;
|
|
1038
1353
|
return (accepted !== undefined && options.adapter.reached(room.locator, roomVersion, accepted));
|
|
1039
1354
|
});
|
|
1040
|
-
|
|
1355
|
+
let settled;
|
|
1356
|
+
try {
|
|
1357
|
+
settled = await settle(local, room.locator, prior, confirmed, queue.filter((batch) => !accepted.includes(batch)), { room: true, ...(stored === undefined ? {} : { stored }) });
|
|
1358
|
+
}
|
|
1359
|
+
catch (error) {
|
|
1360
|
+
if (!exceeded(error))
|
|
1361
|
+
throw error;
|
|
1362
|
+
await exceededOn(room.locator, grownBy(confirmed.record, prior?.confirmed.record));
|
|
1363
|
+
caughtUpTo(room.locator);
|
|
1364
|
+
return true;
|
|
1365
|
+
}
|
|
1366
|
+
const set = settled();
|
|
1041
1367
|
if (stored !== undefined) {
|
|
1042
1368
|
transfers.delete(room.locator);
|
|
1043
1369
|
catching.delete(room.locator);
|
|
@@ -1045,6 +1371,7 @@ export function composeRuntime(options) {
|
|
|
1045
1371
|
for (const batch of accepted)
|
|
1046
1372
|
pendings.delete(batch.id);
|
|
1047
1373
|
stale.delete(room.locator);
|
|
1374
|
+
unindexable.delete(room.locator);
|
|
1048
1375
|
if (unreadable.delete(room.locator))
|
|
1049
1376
|
scopedRefusals.delete(room.locator);
|
|
1050
1377
|
sourceRefusals.delete(room.locator);
|
|
@@ -1453,7 +1780,18 @@ export function composeRuntime(options) {
|
|
|
1453
1780
|
stage: (record, page) => exclusive(async () => {
|
|
1454
1781
|
if (!live())
|
|
1455
1782
|
return false;
|
|
1456
|
-
|
|
1783
|
+
const bytes = layerBytes(page);
|
|
1784
|
+
if (!(await admitted(local, record.locator, bytes)))
|
|
1785
|
+
return false;
|
|
1786
|
+
try {
|
|
1787
|
+
await local.stage(record, page);
|
|
1788
|
+
}
|
|
1789
|
+
catch (error) {
|
|
1790
|
+
if (!exceeded(error))
|
|
1791
|
+
throw error;
|
|
1792
|
+
await exceededOn(record.locator, bytes);
|
|
1793
|
+
return false;
|
|
1794
|
+
}
|
|
1457
1795
|
transfers.set(record.locator, record);
|
|
1458
1796
|
readSide.recover();
|
|
1459
1797
|
return true;
|
|
@@ -1503,7 +1841,7 @@ export function composeRuntime(options) {
|
|
|
1503
1841
|
}
|
|
1504
1842
|
async function advance(room, local, authorized, live) {
|
|
1505
1843
|
await referenced(room.locator);
|
|
1506
|
-
if (!live())
|
|
1844
|
+
if (!live() || paused.has(room.locator))
|
|
1507
1845
|
return unconfirmed;
|
|
1508
1846
|
const held = replicas.get(room.locator);
|
|
1509
1847
|
if (held !== undefined) {
|
|
@@ -1984,15 +2322,16 @@ export function composeRuntime(options) {
|
|
|
1984
2322
|
});
|
|
1985
2323
|
}
|
|
1986
2324
|
async function synchronize(listed, local, progress, attempt, live) {
|
|
1987
|
-
const
|
|
2325
|
+
const acquired = listed.filter(acquires);
|
|
2326
|
+
const prepared = await Promise.allSettled(acquired.map((room) => prepareRoom(room, local, progress)));
|
|
1988
2327
|
if (!live())
|
|
1989
2328
|
return;
|
|
1990
|
-
const ready =
|
|
2329
|
+
const ready = acquired.filter((_, index) => {
|
|
1991
2330
|
const outcome = prepared[index];
|
|
1992
2331
|
return outcome?.status === 'fulfilled' && outcome.value;
|
|
1993
2332
|
});
|
|
1994
2333
|
if (attempt === 'opened') {
|
|
1995
|
-
options.adapter.roomStream?.hold(heldRooms(ready));
|
|
2334
|
+
options.adapter.roomStream?.hold(heldRooms(ready), heldDemand(acquired));
|
|
1996
2335
|
handed = true;
|
|
1997
2336
|
}
|
|
1998
2337
|
discovering?.release();
|
|
@@ -2457,7 +2796,8 @@ export function composeRuntime(options) {
|
|
|
2457
2796
|
if (status.providerUnreachable)
|
|
2458
2797
|
conversations.reconnected();
|
|
2459
2798
|
const reading = { complete: true, reachable: true };
|
|
2460
|
-
|
|
2799
|
+
if (demand.conversations)
|
|
2800
|
+
await conversations.catchUp(reading);
|
|
2461
2801
|
if (progress.reachable && reading.reachable)
|
|
2462
2802
|
await conversations.resend(reading);
|
|
2463
2803
|
if (!reading.reachable) {
|
|
@@ -2520,7 +2860,7 @@ export function composeRuntime(options) {
|
|
|
2520
2860
|
return;
|
|
2521
2861
|
const room = discovered.get(locator);
|
|
2522
2862
|
const held = replicas.get(locator);
|
|
2523
|
-
if (room === undefined || room.replica.admission !== 'admitted')
|
|
2863
|
+
if (room === undefined || room.replica.admission !== 'admitted' || !acquires(room))
|
|
2524
2864
|
return;
|
|
2525
2865
|
if (held !== undefined &&
|
|
2526
2866
|
(held.confirmed.record.replica.selection !== room.replica.selection ||
|
|
@@ -2597,6 +2937,7 @@ export function composeRuntime(options) {
|
|
|
2597
2937
|
haltedRefusal = false;
|
|
2598
2938
|
if (mode !== serviceMode) {
|
|
2599
2939
|
serviceMode = mode;
|
|
2940
|
+
queueReadiness();
|
|
2600
2941
|
journal.record(`service mode ${mode}`);
|
|
2601
2942
|
for (const listener of serviceModeListeners)
|
|
2602
2943
|
listener(mode);
|
|
@@ -2609,6 +2950,8 @@ export function composeRuntime(options) {
|
|
|
2609
2950
|
if (stream === undefined || withdrawConversations !== undefined)
|
|
2610
2951
|
return;
|
|
2611
2952
|
withdrawConversations = stream.observe((event) => {
|
|
2953
|
+
if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted')
|
|
2954
|
+
queueReadiness();
|
|
2612
2955
|
if (conversations.receive(event)) {
|
|
2613
2956
|
void ready.then(scheduleCurrency);
|
|
2614
2957
|
return;
|
|
@@ -2631,7 +2974,7 @@ export function composeRuntime(options) {
|
|
|
2631
2974
|
if (!live())
|
|
2632
2975
|
return;
|
|
2633
2976
|
await conversations.reconcile();
|
|
2634
|
-
if (conversations.current()) {
|
|
2977
|
+
if (conversations.current() || !demand.conversations) {
|
|
2635
2978
|
show();
|
|
2636
2979
|
return;
|
|
2637
2980
|
}
|
|
@@ -2649,6 +2992,11 @@ export function composeRuntime(options) {
|
|
|
2649
2992
|
if (closed)
|
|
2650
2993
|
return;
|
|
2651
2994
|
closed = true;
|
|
2995
|
+
if (narrowing !== null)
|
|
2996
|
+
clearTimeout(narrowing);
|
|
2997
|
+
if (rechecking !== null)
|
|
2998
|
+
clearInterval(rechecking);
|
|
2999
|
+
readinessWatches.clear();
|
|
2652
3000
|
withdrawStream?.();
|
|
2653
3001
|
withdrawStream = undefined;
|
|
2654
3002
|
withdrawConversations?.();
|
|
@@ -2709,40 +3057,55 @@ export function composeRuntime(options) {
|
|
|
2709
3057
|
const cover = covered(filter);
|
|
2710
3058
|
awaitPreparation(cover);
|
|
2711
3059
|
const pending = 'pending' in cover ? [...cover.pending] : [];
|
|
2712
|
-
const
|
|
3060
|
+
const awaited = new Set('pending' in cover ? cover.pending.map(({ room }) => room) : []);
|
|
3061
|
+
const held = covered({
|
|
3062
|
+
...filter,
|
|
3063
|
+
rooms: reachedRooms(filter).filter((room) => !awaited.has(room)),
|
|
3064
|
+
});
|
|
3065
|
+
const covering = 'pending' in held ? [] : held.coverage.rooms;
|
|
2713
3066
|
const unindexed = covering
|
|
2714
3067
|
.filter(({ room }) => !searchIndex.rooms().has(room))
|
|
2715
3068
|
.map(({ room }) => room);
|
|
2716
3069
|
for (const room of unindexed)
|
|
2717
3070
|
pending.push({ room, awaits: 'index' });
|
|
2718
|
-
const
|
|
3071
|
+
const listed = conversations.searchable();
|
|
2719
3072
|
const named = reach !== null && !reach.conversations
|
|
2720
3073
|
? []
|
|
2721
|
-
: (plan.conversations ??
|
|
3074
|
+
: (plan.conversations ?? listed).filter((one) => listed.includes(one) && conversations.acquired(one));
|
|
2722
3075
|
const waiting = named.filter((one) => !searchIndex.conversations().has(one));
|
|
2723
3076
|
for (const conversation of waiting)
|
|
2724
3077
|
pending.push({ conversation, awaits: 'index' });
|
|
2725
3078
|
if (unindexed.length > 0 || waiting.length > 0)
|
|
2726
3079
|
index(unindexed, waiting);
|
|
2727
|
-
|
|
2728
|
-
|
|
3080
|
+
const complete = pending.length === 0 && !('pending' in cover);
|
|
3081
|
+
const answering = complete
|
|
3082
|
+
? filter
|
|
3083
|
+
: {
|
|
3084
|
+
...filter,
|
|
3085
|
+
rooms: covering
|
|
3086
|
+
.filter(({ room }) => searchIndex.rooms().has(room))
|
|
3087
|
+
.map(({ room }) => room),
|
|
3088
|
+
};
|
|
3089
|
+
const answered = covered(answering);
|
|
3090
|
+
if ('pending' in answered)
|
|
3091
|
+
continue;
|
|
3092
|
+
const searchedConversations = named.filter((one) => searchIndex.conversations().has(one));
|
|
2729
3093
|
const seen = searchIndex.changes();
|
|
2730
3094
|
await searchIndex.caughtUp();
|
|
2731
|
-
const selected = plan.documents === undefined && reach === null ? null : await rowsQuery(
|
|
3095
|
+
const selected = plan.documents === undefined && reach === null ? null : await rowsQuery(answering);
|
|
2732
3096
|
if (selected !== null && 'pending' in selected)
|
|
2733
|
-
|
|
3097
|
+
continue;
|
|
2734
3098
|
if (searchIndex.changes() !== seen ||
|
|
2735
|
-
JSON.stringify(covered(
|
|
3099
|
+
JSON.stringify(covered(answering)) !== JSON.stringify(answered))
|
|
2736
3100
|
continue;
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
overlay: cover.coverage.overlay,
|
|
2744
|
-
},
|
|
3101
|
+
const hits = searchIndex.search(plan, selected === null ? null : selected.documents, searchedConversations, complete ? null : new Set(answering.rooms));
|
|
3102
|
+
const coverage = {
|
|
3103
|
+
rooms: answered.coverage.rooms,
|
|
3104
|
+
conversations: searchedConversations,
|
|
3105
|
+
index: searchIndex.generation(),
|
|
3106
|
+
overlay: answered.coverage.overlay,
|
|
2745
3107
|
};
|
|
3108
|
+
return complete ? { ...hits, coverage } : { pending, ...hits, coverage };
|
|
2746
3109
|
}
|
|
2747
3110
|
}
|
|
2748
3111
|
const watch = (topic, input, deliver) => {
|
|
@@ -2789,6 +3152,9 @@ export function composeRuntime(options) {
|
|
|
2789
3152
|
presentationListeners.delete(listener);
|
|
2790
3153
|
};
|
|
2791
3154
|
}
|
|
3155
|
+
if (topic === 'readiness') {
|
|
3156
|
+
return watchReadiness({ target: '', code: 'stored', capacity: null }, null, deliver);
|
|
3157
|
+
}
|
|
2792
3158
|
if (topic === 'rooms') {
|
|
2793
3159
|
roomListeners.add(deliver);
|
|
2794
3160
|
deliver(listed());
|
|
@@ -2955,7 +3321,13 @@ export function composeRuntime(options) {
|
|
|
2955
3321
|
return journal.export();
|
|
2956
3322
|
},
|
|
2957
3323
|
persistence() {
|
|
2958
|
-
return
|
|
3324
|
+
return persistence === 'granted';
|
|
3325
|
+
},
|
|
3326
|
+
async retry() {
|
|
3327
|
+
journal.record('retry');
|
|
3328
|
+
unindexable.clear();
|
|
3329
|
+
queueReadiness();
|
|
3330
|
+
resume();
|
|
2959
3331
|
},
|
|
2960
3332
|
observe: observing(watch),
|
|
2961
3333
|
async reauthorize() {
|
|
@@ -2982,6 +3354,22 @@ export function composeRuntime(options) {
|
|
|
2982
3354
|
query: (plan) => rowsQuery(plan),
|
|
2983
3355
|
watchPlan,
|
|
2984
3356
|
search: searched,
|
|
3357
|
+
searching,
|
|
3358
|
+
watchReadiness,
|
|
3359
|
+
};
|
|
3360
|
+
return {
|
|
3361
|
+
md,
|
|
3362
|
+
stop,
|
|
3363
|
+
lose: loseRooms,
|
|
3364
|
+
watch,
|
|
3365
|
+
acquire,
|
|
3366
|
+
persisted(answer) {
|
|
3367
|
+
if (answer === persistence)
|
|
3368
|
+
return;
|
|
3369
|
+
persistence = answer;
|
|
3370
|
+
journal.record(`persistence ${answer}`);
|
|
3371
|
+
queueReadiness();
|
|
3372
|
+
},
|
|
3373
|
+
within: (reach, port) => narrowed(reachable, reach, port),
|
|
2985
3374
|
};
|
|
2986
|
-
return { md, stop, lose: loseRooms, watch, within: (reach) => narrowed(reachable, reach) };
|
|
2987
3375
|
}
|