@animalabs/agent-framework 0.31.0 → 0.33.0
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 +204 -14
- package/dist/src/admission/admit.d.ts +18 -0
- package/dist/src/admission/admit.d.ts.map +1 -1
- package/dist/src/admission/admit.js +16 -3
- package/dist/src/admission/admit.js.map +1 -1
- package/dist/src/agent.d.ts +185 -78
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +279 -102
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/mcp-server-core.d.ts.map +1 -1
- package/dist/src/api/mcp-server-core.js +50 -12
- package/dist/src/api/mcp-server-core.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +4 -20
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/code-execution/isolate/scripts.d.ts +12 -8
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +24 -0
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/config-checks.d.ts +7 -0
- package/dist/src/config-checks.d.ts.map +1 -1
- package/dist/src/config-checks.js +14 -0
- package/dist/src/config-checks.js.map +1 -1
- package/dist/src/context-manager-errors.d.ts +25 -0
- package/dist/src/context-manager-errors.d.ts.map +1 -0
- package/dist/src/context-manager-errors.js +31 -0
- package/dist/src/context-manager-errors.js.map +1 -0
- package/dist/src/context-options.d.ts +67 -0
- package/dist/src/context-options.d.ts.map +1 -0
- package/dist/src/context-options.js +85 -0
- package/dist/src/context-options.js.map +1 -0
- package/dist/src/deferred-import.d.ts +93 -0
- package/dist/src/deferred-import.d.ts.map +1 -0
- package/dist/src/deferred-import.js +177 -0
- package/dist/src/deferred-import.js.map +1 -0
- package/dist/src/dendrite/audience.d.ts +223 -0
- package/dist/src/dendrite/audience.d.ts.map +1 -0
- package/dist/src/dendrite/audience.js +202 -0
- package/dist/src/dendrite/audience.js.map +1 -0
- package/dist/src/dendrite/index.d.ts +5 -3
- package/dist/src/dendrite/index.d.ts.map +1 -1
- package/dist/src/dendrite/index.js +2 -1
- package/dist/src/dendrite/index.js.map +1 -1
- package/dist/src/dendrite/presets.d.ts +10 -5
- package/dist/src/dendrite/presets.d.ts.map +1 -1
- package/dist/src/dendrite/presets.js +17 -5
- package/dist/src/dendrite/presets.js.map +1 -1
- package/dist/src/dendrite/registry.d.ts +62 -7
- package/dist/src/dendrite/registry.d.ts.map +1 -1
- package/dist/src/dendrite/registry.js +179 -10
- package/dist/src/dendrite/registry.js.map +1 -1
- package/dist/src/dendrite/types.d.ts +91 -8
- package/dist/src/dendrite/types.d.ts.map +1 -1
- package/dist/src/foreign-rounds.d.ts +69 -0
- package/dist/src/foreign-rounds.d.ts.map +1 -0
- package/dist/src/foreign-rounds.js +152 -0
- package/dist/src/foreign-rounds.js.map +1 -0
- package/dist/src/framework.d.ts +1977 -820
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +7571 -3457
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +85 -15
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +421 -47
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +1 -1
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/gate/rule-scope.d.ts +30 -0
- package/dist/src/gate/rule-scope.d.ts.map +1 -0
- package/dist/src/gate/rule-scope.js +41 -0
- package/dist/src/gate/rule-scope.js.map +1 -0
- package/dist/src/gate/suppress-script-worker.d.ts +2 -0
- package/dist/src/gate/suppress-script-worker.d.ts.map +1 -0
- package/dist/src/gate/suppress-script-worker.js +103 -0
- package/dist/src/gate/suppress-script-worker.js.map +1 -0
- package/dist/src/gate/suppress-scripts.d.ts +105 -0
- package/dist/src/gate/suppress-scripts.d.ts.map +1 -0
- package/dist/src/gate/suppress-scripts.js +403 -0
- package/dist/src/gate/suppress-scripts.js.map +1 -0
- package/dist/src/gate/suppression-review.d.ts +143 -0
- package/dist/src/gate/suppression-review.d.ts.map +1 -0
- package/dist/src/gate/suppression-review.js +463 -0
- package/dist/src/gate/suppression-review.js.map +1 -0
- package/dist/src/gate/suppression-stats.d.ts +125 -0
- package/dist/src/gate/suppression-stats.d.ts.map +1 -0
- package/dist/src/gate/suppression-stats.js +214 -0
- package/dist/src/gate/suppression-stats.js.map +1 -0
- package/dist/src/gate/types.d.ts +135 -7
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/gate/wake-onboarding.d.ts +119 -0
- package/dist/src/gate/wake-onboarding.d.ts.map +1 -0
- package/dist/src/gate/wake-onboarding.js +277 -0
- package/dist/src/gate/wake-onboarding.js.map +1 -0
- package/dist/src/held-writes.d.ts +31 -23
- package/dist/src/held-writes.d.ts.map +1 -1
- package/dist/src/held-writes.js +44 -59
- package/dist/src/held-writes.js.map +1 -1
- package/dist/src/index.d.ts +5 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/jsonrpc-envelope.d.ts +69 -0
- package/dist/src/jsonrpc-envelope.d.ts.map +1 -0
- package/dist/src/jsonrpc-envelope.js +66 -0
- package/dist/src/jsonrpc-envelope.js.map +1 -0
- package/dist/src/mcpl/channel-registry.d.ts +131 -15
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +290 -46
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/checkpoint-manager.d.ts +2 -4
- package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
- package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
- package/dist/src/mcpl/feature-set-manager.d.ts +2 -4
- package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
- package/dist/src/mcpl/feature-set-manager.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +2 -1
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +2 -1
- package/dist/src/mcpl/inbound-source.js.map +1 -1
- package/dist/src/mcpl/inference-router.d.ts +6 -6
- package/dist/src/mcpl/inference-router.d.ts.map +1 -1
- package/dist/src/mcpl/inference-router.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +132 -22
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +105 -15
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/push-handler.d.ts +9 -7
- package/dist/src/mcpl/push-handler.d.ts.map +1 -1
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +13 -10
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +134 -56
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/tool-classes.d.ts.map +1 -1
- package/dist/src/mcpl/tool-classes.js +3 -0
- package/dist/src/mcpl/tool-classes.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +8 -2
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/module-registry.d.ts +24 -1
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +68 -18
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts +45 -8
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +237 -86
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/history/index.d.ts +59 -0
- package/dist/src/modules/history/index.d.ts.map +1 -1
- package/dist/src/modules/history/index.js +432 -67
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/history/semantic.d.ts +6 -5
- package/dist/src/modules/history/semantic.d.ts.map +1 -1
- package/dist/src/modules/history/semantic.js +15 -22
- package/dist/src/modules/history/semantic.js.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.d.ts +1 -1
- package/dist/src/modules/workspace/disk-agreement.js +1 -1
- package/dist/src/modules/workspace/index.d.ts +28 -7
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +76 -22
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/notice-tags.d.ts +10 -1
- package/dist/src/notice-tags.d.ts.map +1 -1
- package/dist/src/notice-tags.js +10 -1
- package/dist/src/notice-tags.js.map +1 -1
- package/dist/src/operator-log.d.ts +36 -3
- package/dist/src/operator-log.d.ts.map +1 -1
- package/dist/src/operator-log.js +2 -2
- package/dist/src/operator-log.js.map +1 -1
- package/dist/src/presentation.d.ts +746 -0
- package/dist/src/presentation.d.ts.map +1 -0
- package/dist/src/presentation.js +1435 -0
- package/dist/src/presentation.js.map +1 -0
- package/dist/src/prose-drafts.d.ts +15 -11
- package/dist/src/prose-drafts.d.ts.map +1 -1
- package/dist/src/prose-drafts.js +16 -12
- package/dist/src/prose-drafts.js.map +1 -1
- package/dist/src/read-marks.d.ts +192 -0
- package/dist/src/read-marks.d.ts.map +1 -0
- package/dist/src/read-marks.js +242 -0
- package/dist/src/read-marks.js.map +1 -0
- package/dist/src/recovery/offline-branch.d.ts +91 -6
- package/dist/src/recovery/offline-branch.d.ts.map +1 -1
- package/dist/src/recovery/offline-branch.js +273 -29
- package/dist/src/recovery/offline-branch.js.map +1 -1
- package/dist/src/recovery/recover-cli.js +22 -3
- package/dist/src/recovery/recover-cli.js.map +1 -1
- package/dist/src/send-tools.d.ts +5 -9
- package/dist/src/send-tools.d.ts.map +1 -1
- package/dist/src/send-tools.js +40 -7
- package/dist/src/send-tools.js.map +1 -1
- package/dist/src/stored-injections.d.ts +2 -2
- package/dist/src/stored-injections.js +2 -2
- package/dist/src/stored-removal.d.ts +17 -4
- package/dist/src/stored-removal.d.ts.map +1 -1
- package/dist/src/stored-removal.js +29 -5
- package/dist/src/stored-removal.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts +12 -1
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +42 -8
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +1159 -79
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +3709 -363
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +114 -17
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/tune-out/tools.js +87 -27
- package/dist/src/tune-out/tools.js.map +1 -1
- package/dist/src/tune-out/window-anchor.d.ts +143 -0
- package/dist/src/tune-out/window-anchor.d.ts.map +1 -0
- package/dist/src/tune-out/window-anchor.js +202 -0
- package/dist/src/tune-out/window-anchor.js.map +1 -0
- package/dist/src/types/agent.d.ts +86 -31
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +2 -0
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +43 -21
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +180 -69
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +510 -36
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/admission/admit.ts +32 -3
- package/src/agent.ts +323 -120
- package/src/api/mcp-server-core.ts +54 -17
- package/src/api/server.ts +4 -23
- package/src/code-execution/isolate/scripts.ts +28 -3
- package/src/config-checks.ts +14 -0
- package/src/context-manager-errors.ts +37 -0
- package/src/context-options.ts +96 -0
- package/src/deferred-import.ts +239 -0
- package/src/dendrite/audience.ts +315 -0
- package/src/dendrite/index.ts +14 -2
- package/src/dendrite/presets.ts +18 -6
- package/src/dendrite/registry.ts +209 -12
- package/src/dendrite/types.ts +92 -8
- package/src/foreign-rounds.ts +199 -0
- package/src/framework.ts +7814 -3585
- package/src/gate/event-gate.ts +428 -65
- package/src/gate/index.ts +3 -0
- package/src/gate/rule-scope.ts +45 -0
- package/src/gate/suppress-script-worker.ts +89 -0
- package/src/gate/suppress-scripts.ts +414 -0
- package/src/gate/suppression-review.ts +517 -0
- package/src/gate/suppression-stats.ts +314 -0
- package/src/gate/types.ts +141 -7
- package/src/gate/wake-onboarding.ts +309 -0
- package/src/held-writes.ts +49 -60
- package/src/index.ts +9 -2
- package/src/jsonrpc-envelope.ts +81 -0
- package/src/mcpl/channel-registry.ts +353 -85
- package/src/mcpl/checkpoint-manager.ts +3 -2
- package/src/mcpl/feature-set-manager.ts +2 -1
- package/src/mcpl/inbound-source.ts +2 -1
- package/src/mcpl/inference-router.ts +7 -4
- package/src/mcpl/push-coalescer.ts +193 -34
- package/src/mcpl/push-handler.ts +10 -5
- package/src/mcpl/server-connection.ts +141 -60
- package/src/mcpl/tool-classes.ts +3 -0
- package/src/mcpl/types.ts +8 -2
- package/src/module-registry.ts +73 -17
- package/src/modules/discord/index.ts +229 -81
- package/src/modules/history/index.ts +439 -70
- package/src/modules/history/semantic.ts +15 -21
- package/src/modules/workspace/disk-agreement.ts +1 -1
- package/src/modules/workspace/index.ts +85 -23
- package/src/notice-tags.ts +10 -1
- package/src/operator-log.ts +36 -3
- package/src/presentation.ts +1676 -0
- package/src/prose-drafts.ts +16 -12
- package/src/read-marks.ts +328 -0
- package/src/recovery/offline-branch.ts +346 -32
- package/src/recovery/recover-cli.ts +21 -3
- package/src/send-tools.ts +40 -7
- package/src/stored-injections.ts +2 -2
- package/src/stored-removal.ts +31 -7
- package/src/tool-result-guard.ts +41 -7
- package/src/tune-out/coordinator.ts +4089 -457
- package/src/tune-out/tools.ts +160 -43
- package/src/tune-out/window-anchor.ts +277 -0
- package/src/types/agent.ts +90 -31
- package/src/types/events.ts +2 -0
- package/src/types/framework.ts +43 -21
- package/src/types/module.ts +181 -68
- package/src/types/trace.ts +542 -40
- package/dist/src/reasoning-loss-notice.d.ts +0 -23
- package/dist/src/reasoning-loss-notice.d.ts.map +0 -1
- package/dist/src/reasoning-loss-notice.js +0 -52
- package/dist/src/reasoning-loss-notice.js.map +0 -1
- package/dist/src/reasoning-loss.d.ts +0 -24
- package/dist/src/reasoning-loss.d.ts.map +0 -1
- package/dist/src/reasoning-loss.js +0 -102
- package/dist/src/reasoning-loss.js.map +0 -1
- package/src/reasoning-loss-notice.ts +0 -64
- package/src/reasoning-loss.ts +0 -120
|
@@ -0,0 +1,1435 @@
|
|
|
1
|
+
import { continuesBody, MessageStore } from '@animalabs/context-manager';
|
|
2
|
+
import { audienceOf, heldFromResidents, observedRowFor, suppressedRow } from './dendrite/audience.js';
|
|
3
|
+
import { sameSlot } from './dendrite/registry.js';
|
|
4
|
+
import { isAnotherAgentsInjection } from './stored-injections.js';
|
|
5
|
+
import { nameAsStored } from './text-as-stored.js';
|
|
6
|
+
export { heldFromResidents, suppressedRow } from './dendrite/audience.js';
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Pure pieces the tool boundary and the drain share
|
|
9
|
+
// ============================================================================
|
|
10
|
+
/** Whether a stored row can join a live stream at a tool boundary: no tool
|
|
11
|
+
* blocks (the membrane's tool cycle) and not named as the agent itself (it
|
|
12
|
+
* would render as an assistant turn, a prefill). The #286 cut. */
|
|
13
|
+
export function isInjectableMidTurn(msg, agent) {
|
|
14
|
+
// Exact, as membrane's role test is: a participant the request assigns
|
|
15
|
+
// the assistant role (the agent's name, and for a derived agent the
|
|
16
|
+
// voice it inherited) renders as the assistant. It's the name as sent,
|
|
17
|
+
// which the boundary takes as the store keeps it.
|
|
18
|
+
return !agent.isOwnParticipant(nameAsStored(msg.participant))
|
|
19
|
+
&& !msg.content.some((b) => b.type === 'tool_use' || b.type === 'tool_result');
|
|
20
|
+
}
|
|
21
|
+
/** What later compiles render from the store for the copies of one write
|
|
22
|
+
* that landed: one copy as it is, and two or more, the shards of a sharded
|
|
23
|
+
* body, as context-manager's autobiographical strategy (the one that
|
|
24
|
+
* shards on ingest) joins a body's consecutive shards: their non-text
|
|
25
|
+
* blocks, then one text block of their joined text. The join is that
|
|
26
|
+
* strategy's `mergeAdjacentBodyGroupRaw`, which context-manager doesn't
|
|
27
|
+
* export, so the rule is copied here. */
|
|
28
|
+
export function asCompiled(landed) {
|
|
29
|
+
if (landed.length === 1)
|
|
30
|
+
return landed[0].content;
|
|
31
|
+
const blocks = landed.flatMap((m) => m.content);
|
|
32
|
+
const text = blocks.filter((b) => b.type === 'text').map((b) => b.text).join('');
|
|
33
|
+
return [...blocks.filter((b) => b.type !== 'text'), { type: 'text', text }];
|
|
34
|
+
}
|
|
35
|
+
/** The row's place in `cm`'s window, or undefined for a row out of it (and every row in legacy mode). */
|
|
36
|
+
export function positionIn(cm, id) {
|
|
37
|
+
return cm.ordinalOf(id);
|
|
38
|
+
}
|
|
39
|
+
/** The place the next row presented in `cm`'s window will take (its index length, exclusive; 0 in legacy mode). */
|
|
40
|
+
export function windowEnd(cm) {
|
|
41
|
+
return cm.indexLength();
|
|
42
|
+
}
|
|
43
|
+
/** Where `cm`'s window stands now, or null in legacy mode, where the window is the record and has no index. */
|
|
44
|
+
export function windowMarkOf(cm) {
|
|
45
|
+
if (cm.windowMode() !== 'presented')
|
|
46
|
+
return null;
|
|
47
|
+
return { branch: cm.currentBranch().name, position: windowEnd(cm) };
|
|
48
|
+
}
|
|
49
|
+
/** A reader's facts as the registry has them now. An agent it doesn't know has no slot and observes nothing. */
|
|
50
|
+
export function readerFacts(registry, name) {
|
|
51
|
+
return factsOf(registry, name, registry.get(name), registry.ownSlotOf(name));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A reader's facts as the registry recorded them: its live record, else its
|
|
55
|
+
* newest ended one (registry.inspect). For a reader that has left, as a
|
|
56
|
+
* released fork or run has by the time its window is read for what it
|
|
57
|
+
* never read (AgentFramework.releasedWindow): readerFacts reads live
|
|
58
|
+
* records only, and would know nothing of it.
|
|
59
|
+
*/
|
|
60
|
+
export function recordedReaderFacts(registry, name) {
|
|
61
|
+
const record = registry.inspect(name);
|
|
62
|
+
return factsOf(registry, name, record, record?.ownSlot ? { ...record.ownSlot } : undefined);
|
|
63
|
+
}
|
|
64
|
+
/** A reader's facts from its record and its own slot (readerFacts, recordedReaderFacts). */
|
|
65
|
+
function factsOf(registry, name, record, ownSlot) {
|
|
66
|
+
const observed = [];
|
|
67
|
+
for (const edge of record?.relationships.observes ?? []) {
|
|
68
|
+
const slot = registry.observedSlotOf(edge);
|
|
69
|
+
if (slot)
|
|
70
|
+
observed.push({ slot, edge, owners: registry.slotOwners(slot) });
|
|
71
|
+
}
|
|
72
|
+
const derivedAt = record?.inherit?.mode === 'shared' ? record.inherit.atSequence : undefined;
|
|
73
|
+
return {
|
|
74
|
+
name,
|
|
75
|
+
ownSlot,
|
|
76
|
+
observed,
|
|
77
|
+
hidesHeld: ownSlot?.namespace === null,
|
|
78
|
+
...(derivedAt !== undefined ? { derivedAt } : {}),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* What one row is to one reader, from the row's stamp, the reader's facts,
|
|
83
|
+
* and `slot`, the slot the row is stored in: the reader's own (`'own'`, or
|
|
84
|
+
* a ref sameSlot as its `ownSlot`), else a slot it may observe. undefined,
|
|
85
|
+
* a slot its caller couldn't name, is elsewhere.
|
|
86
|
+
*
|
|
87
|
+
* A row a suppress rule hid (suppressedRow) is elsewhere to every reader,
|
|
88
|
+
* in any slot: no view the framework sets renders it.
|
|
89
|
+
*
|
|
90
|
+
* In its own slot, with `stamp = audienceOf(metadata)`:
|
|
91
|
+
* - its own row (`ownRowOf` names it): own;
|
|
92
|
+
* - another agent's own row: an arrival when one of the reader's edges
|
|
93
|
+
* names that agent and reads this slot (observesOwnRows: a resident's
|
|
94
|
+
* `AgentConfig.observes`, the relationship decision 1 says another
|
|
95
|
+
* agent's rounds reach an agent only through), unless it is that agent's
|
|
96
|
+
* stored context injection, which the reader's view leaves out;
|
|
97
|
+
* elsewhere otherwise, as it is for every resident by default;
|
|
98
|
+
* - a row whose audience names it: an arrival, unless it is held and the
|
|
99
|
+
* reader leaves held rows out, or it is another agent's stored injection,
|
|
100
|
+
* which the reader's view leaves out too (what the reader's own view
|
|
101
|
+
* leaves out is never its arrival: main's #853 asks that view, agentSees,
|
|
102
|
+
* at a tool boundary);
|
|
103
|
+
* - a row stamped otherwise (mail for a co-resident, a held row): elsewhere;
|
|
104
|
+
* - a row with no stamp (stored by a host's writer that doesn't stamp): an
|
|
105
|
+
* arrival for the slot's owners, as it reads today, unless it is a shared
|
|
106
|
+
* derivation's inheritance (at or before `derivedAt`), held for a reader
|
|
107
|
+
* that leaves held rows out, or another agent's stored injection.
|
|
108
|
+
*
|
|
109
|
+
* In another slot: an arrival when an edge of the reader's that reads that
|
|
110
|
+
* slot shows it, by that edge's rules and that slot's owners
|
|
111
|
+
* (observedRowFor); elsewhere when none does, or the reader has no edge on
|
|
112
|
+
* the slot. A pure function of its arguments: it runs for a released agent,
|
|
113
|
+
* which has no live manager, too.
|
|
114
|
+
*/
|
|
115
|
+
export function bearingOf(row, reader, slot) {
|
|
116
|
+
const metadata = row.metadata;
|
|
117
|
+
if (slot === undefined)
|
|
118
|
+
return 'elsewhere';
|
|
119
|
+
if (suppressedRow(metadata))
|
|
120
|
+
return 'elsewhere';
|
|
121
|
+
if (slot !== 'own' && !sameSlot(slot, reader.ownSlot)) {
|
|
122
|
+
const shown = reader.observed.some((seen) => sameSlot(seen.slot, slot)
|
|
123
|
+
&& observedRowFor(seen.edge, row, { observer: reader.name, owners: seen.owners }));
|
|
124
|
+
return shown ? 'arrival' : 'elsewhere';
|
|
125
|
+
}
|
|
126
|
+
const stamp = audienceOf(metadata);
|
|
127
|
+
if (stamp) {
|
|
128
|
+
if (stamp.ownRowOf !== undefined) {
|
|
129
|
+
if (stamp.ownRowOf === reader.name)
|
|
130
|
+
return 'own';
|
|
131
|
+
return observesInOwnSlot(reader, stamp.ownRowOf) && !isAnotherAgentsInjection(metadata, reader.name) ? 'arrival' : 'elsewhere';
|
|
132
|
+
}
|
|
133
|
+
if (!stamp.audience.includes(reader.name))
|
|
134
|
+
return 'elsewhere';
|
|
135
|
+
if (isAnotherAgentsInjection(metadata, reader.name))
|
|
136
|
+
return 'elsewhere';
|
|
137
|
+
return reader.hidesHeld && heldFromResidents(metadata) ? 'elsewhere' : 'arrival';
|
|
138
|
+
}
|
|
139
|
+
if (reader.derivedAt !== undefined && row.sequence <= reader.derivedAt)
|
|
140
|
+
return 'elsewhere';
|
|
141
|
+
if (reader.hidesHeld && heldFromResidents(metadata))
|
|
142
|
+
return 'elsewhere';
|
|
143
|
+
if (isAnotherAgentsInjection(metadata, reader.name))
|
|
144
|
+
return 'elsewhere';
|
|
145
|
+
return 'arrival';
|
|
146
|
+
}
|
|
147
|
+
/** Whether one of the reader's edges names `owner` and reads the reader's own slot: a co-resident it observes. */
|
|
148
|
+
function observesInOwnSlot(reader, owner) {
|
|
149
|
+
return reader.observed.some((seen) => seen.edge.agent === owner && sameSlot(seen.slot, reader.ownSlot));
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Whether a reader is shown `owner`'s own rows through an observe edge, as
|
|
153
|
+
* bearingOf reads its edges: an edge on another slot that `owner` owns, or
|
|
154
|
+
* an edge that names `owner` and reads the reader's own slot (a resident
|
|
155
|
+
* observing a co-resident, AgentConfig.observes). An edge on the reader's
|
|
156
|
+
* own slot shows only the agent it names, though every resident owns that
|
|
157
|
+
* slot.
|
|
158
|
+
*/
|
|
159
|
+
export function observesOwnRows(reader, owner) {
|
|
160
|
+
return reader.observed.some((seen) => (sameSlot(seen.slot, reader.ownSlot)
|
|
161
|
+
? seen.edge.agent === owner
|
|
162
|
+
: seen.owners.includes(owner)));
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The slots besides its own a reader's context merges into its record: the
|
|
166
|
+
* slots its edges read on its context's branch (no `branch` of their own),
|
|
167
|
+
* each once. The framework opens an observer's context with one auxiliary
|
|
168
|
+
* view per such slot (createSubconsciousAgent, reopenAgentContext); a slot
|
|
169
|
+
* on a branch of its own (a shared fork's) can't be merged so.
|
|
170
|
+
*/
|
|
171
|
+
function mergedSlotsOf(reader) {
|
|
172
|
+
const slots = [];
|
|
173
|
+
for (const { slot } of reader.observed) {
|
|
174
|
+
if (slot.branch !== undefined || sameSlot(slot, reader.ownSlot))
|
|
175
|
+
continue;
|
|
176
|
+
if (!slots.some((s) => sameSlot(s, slot)))
|
|
177
|
+
slots.push(slot);
|
|
178
|
+
}
|
|
179
|
+
return slots;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Whether a row stamped for `audience` and stored in `slot` is meant only for
|
|
183
|
+
* agents that can't read it: its audience names someone, and none of them
|
|
184
|
+
* reads the slot, as an owner or through an observe edge (registry
|
|
185
|
+
* readersOf). Such a row is stored as named, never widened (no window
|
|
186
|
+
* presents it by its audience), and traced as a routing fault. A row whose
|
|
187
|
+
* audience names an observer of the slot is no fault: its edge shows it
|
|
188
|
+
* (observedRowFor), as one naming an owner is presented in that owner's
|
|
189
|
+
* window.
|
|
190
|
+
*/
|
|
191
|
+
export function audienceFault(audience, slot, registry) {
|
|
192
|
+
if (audience.length === 0 || slot === undefined)
|
|
193
|
+
return false;
|
|
194
|
+
const readers = registry.readersOf(slot);
|
|
195
|
+
return !audience.some((name) => readers.includes(name));
|
|
196
|
+
}
|
|
197
|
+
const describe = (error) => (error instanceof Error ? error.message : String(error));
|
|
198
|
+
/**
|
|
199
|
+
* The presentation index's decisions, over each live agent's context
|
|
200
|
+
* manager. The framework constructs it in create() and calls it at each
|
|
201
|
+
* presentation point.
|
|
202
|
+
*/
|
|
203
|
+
export class AgentPresentation {
|
|
204
|
+
host;
|
|
205
|
+
adopted = new WeakMap();
|
|
206
|
+
memos = new WeakMap();
|
|
207
|
+
/** Bumped when a registration may have changed what a row is to someone: every memo is then stale. */
|
|
208
|
+
generation = 0;
|
|
209
|
+
/** Rows handed each reader fork as framing (noteFramed), in memory. */
|
|
210
|
+
framed = new Map();
|
|
211
|
+
/** Reader forks whose framing may not all be in their window yet: framed before they were taken on, or whose framing's presentation failed. */
|
|
212
|
+
framedPending = new Set();
|
|
213
|
+
listeners = new Map();
|
|
214
|
+
/** Own rows whose presentation at their write failed, per manager (presentOwn), until they enter its window. */
|
|
215
|
+
failedOwn = new WeakMap();
|
|
216
|
+
/** Arrivals a rollback took out of each manager's window, kept out of it at rest until the agent's next turn start (holdUntilStart), on the branch the rollback left the store on. */
|
|
217
|
+
heldUntilStart = new WeakMap();
|
|
218
|
+
/** Agents the boot's pass at rest left out (repairAndRest's `restExcept`): no point at rest presents their arrivals until their next turn start. */
|
|
219
|
+
restWithheld = new Set();
|
|
220
|
+
/** Read-only handles on the slots a manager merges, by namespace (slotOfRow): made only for a reader whose context merges more than one. */
|
|
221
|
+
slotReaders = new WeakMap();
|
|
222
|
+
constructor(host) {
|
|
223
|
+
this.host = host;
|
|
224
|
+
}
|
|
225
|
+
// --------------------------------------------------------------------------
|
|
226
|
+
// Taking agents on
|
|
227
|
+
// --------------------------------------------------------------------------
|
|
228
|
+
/** Take on every live agent (adopt), then present what each is owed at rest (repairAndRest). */
|
|
229
|
+
boot() {
|
|
230
|
+
for (const agent of this.host.liveAgents())
|
|
231
|
+
this.adopt(agent);
|
|
232
|
+
this.repairAndRest();
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Take an agent on. Once per manager object: a manager in legacy mode has
|
|
236
|
+
* its window written down (presentRows([]), which also finishes a
|
|
237
|
+
* write-down cut short), the stamped rows it took in that are elsewhere to
|
|
238
|
+
* the agent are taken out (retractForeign; where that fails, at the
|
|
239
|
+
* agent's next turn start or point at rest), and the agent's own rows
|
|
240
|
+
* stored and not in its window are presented to it, in record order (an
|
|
241
|
+
* undone turn's rows are not: getUnpresentedMessages leaves them out). A
|
|
242
|
+
* window that can't be
|
|
243
|
+
* written down (another version's index format, or a failed write; not a
|
|
244
|
+
* write-down that threw once its record was written, which reads as
|
|
245
|
+
* presented: writeDown) leaves the agent in legacy mode, traced once,
|
|
246
|
+
* until a move of the store's branch writes its window down on the new
|
|
247
|
+
* branch (rebase): its window is the whole record, as before the index.
|
|
248
|
+
* Then, every time, the agent's own-row sink is installed, one left in
|
|
249
|
+
* legacy mode's too, so the other readers of its slot hear of its rows
|
|
250
|
+
* (presentOwn), and every scan memo is dropped, since a registration can
|
|
251
|
+
* change who reads a slot.
|
|
252
|
+
*/
|
|
253
|
+
adopt(agent) {
|
|
254
|
+
const cm = agent.getContextManager();
|
|
255
|
+
this.generation++;
|
|
256
|
+
let state = this.adopted.get(cm);
|
|
257
|
+
if (!state) {
|
|
258
|
+
state = {};
|
|
259
|
+
this.adopted.set(cm, state);
|
|
260
|
+
this.writeDown(agent, state, 'adopt');
|
|
261
|
+
if (!state.pinnedLegacy) {
|
|
262
|
+
this.retractForeign(agent, 'adopt');
|
|
263
|
+
this.repair(agent);
|
|
264
|
+
// A reader fork is framed before it is taken on (noteFramed at its
|
|
265
|
+
// derivation, adopt at its run's registration): the held rows it was
|
|
266
|
+
// handed enter its window now, after its framing row.
|
|
267
|
+
this.presentFramed(agent);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// In legacy mode too: presentOwn presents nothing for it, and tells the
|
|
271
|
+
// other readers of its slot of its rows (afterStore).
|
|
272
|
+
agent.setOwnRowSink?.((ids) => this.presentOwn(agent, ids));
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* The store's branch moved under an agent taken on (a host's switchBranch,
|
|
276
|
+
* a rollback's or a suppression's switch, a failed surgery's restore).
|
|
277
|
+
* Its manager's window index is the new branch's: on a branch made before
|
|
278
|
+
* the agent's write-down (every branch an earlier release's store holds,
|
|
279
|
+
* or a rollback into rows the write-down presented, which branchAt cuts
|
|
280
|
+
* at their own sequence) there is none, and the window would be the
|
|
281
|
+
* whole record for the rest of the process. So, as at adopt, such a
|
|
282
|
+
* window is written down (presentRows([]), traced as presented at
|
|
283
|
+
* `'rebase'`), the stamped rows it took in that are elsewhere to the
|
|
284
|
+
* agent, a co-resident's own rows presented on the old branch among them,
|
|
285
|
+
* are taken out (retractForeign), and the agent's own rows stored and not
|
|
286
|
+
* in its window are presented to it (the own-row sweep). A window that is presented on the
|
|
287
|
+
* new branch is presented again for an agent left in legacy mode by an
|
|
288
|
+
* earlier branch, its own-row sink installed. A window that can't be
|
|
289
|
+
* written down, or a manager whose strategy couldn't follow the move
|
|
290
|
+
* (`failure`, from its re-initialization by the framework, #353), leaves
|
|
291
|
+
* the agent in legacy mode until the next move, traced as
|
|
292
|
+
* presentation:failed at `'rebase'`. Arrivals aren't presented here: the
|
|
293
|
+
* framework's surgery release presents what each agent is owed. Never
|
|
294
|
+
* throws.
|
|
295
|
+
*/
|
|
296
|
+
rebase(agent, failure) {
|
|
297
|
+
const cm = agent.getContextManager();
|
|
298
|
+
const state = this.adopted.get(cm);
|
|
299
|
+
if (!state)
|
|
300
|
+
return;
|
|
301
|
+
this.generation++;
|
|
302
|
+
this.memos.delete(cm);
|
|
303
|
+
try {
|
|
304
|
+
if (failure !== undefined) {
|
|
305
|
+
state.pinnedLegacy = describe(failure);
|
|
306
|
+
this.host.emitTrace({ type: 'presentation:failed', agentName: agent.name, occasion: 'rebase', error: state.pinnedLegacy, legacy: true });
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (cm.isClosed())
|
|
310
|
+
return;
|
|
311
|
+
delete state.pinnedLegacy;
|
|
312
|
+
if (!this.writeDown(agent, state, 'rebase'))
|
|
313
|
+
return;
|
|
314
|
+
agent.setOwnRowSink?.((ids) => this.presentOwn(agent, ids));
|
|
315
|
+
this.retractForeign(agent, 'rebase');
|
|
316
|
+
this.repair(agent, 'rebase');
|
|
317
|
+
// A reader fork's framing that its window doesn't hold on this branch.
|
|
318
|
+
this.presentFramed(agent);
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
state.pinnedLegacy = describe(error);
|
|
322
|
+
this.host.emitTrace({ type: 'presentation:failed', agentName: agent.name, occasion: 'rebase', error: state.pinnedLegacy, legacy: true });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Write `agent`'s window down where its manager is in legacy mode
|
|
327
|
+
* (presentRows([]), which also finishes a write-down cut short), and say
|
|
328
|
+
* whether the window is presented after it. A write-down that fails, or
|
|
329
|
+
* leaves the manager in legacy mode, pins the agent in legacy mode
|
|
330
|
+
* (`state.pinnedLegacy`), traced as presentation:failed at `occasion`
|
|
331
|
+
* with `legacy`, which the host logs and alerts. A
|
|
332
|
+
* write-down that throws once its record is written (the store's own work
|
|
333
|
+
* after a write, such as a snapshot, failing) has written the window
|
|
334
|
+
* down: the manager reads its index again after any throw, so a window
|
|
335
|
+
* that reads as presented then is taken as one, and the agent is
|
|
336
|
+
* presented for, with a log line. A write-down at a rebase is traced as
|
|
337
|
+
* presented, with the bodies the window holds; a read for that trace
|
|
338
|
+
* which throws is logged and the trace skipped, the window presented.
|
|
339
|
+
*/
|
|
340
|
+
writeDown(agent, state, occasion) {
|
|
341
|
+
const cm = agent.getContextManager();
|
|
342
|
+
let wrote = false;
|
|
343
|
+
if (cm.windowMode() === 'legacy') {
|
|
344
|
+
try {
|
|
345
|
+
cm.presentRows([]);
|
|
346
|
+
wrote = true;
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
if (!readsPresented(cm)) {
|
|
350
|
+
state.pinnedLegacy = describe(error);
|
|
351
|
+
this.host.emitTrace({ type: 'presentation:failed', agentName: agent.name, occasion, error: state.pinnedLegacy, legacy: true });
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
console.warn(`[presentation] ${agent.name}: writing its window down at ${occasion} threw after the window was written (${describe(error)}); its window is presented`);
|
|
355
|
+
wrote = true;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (cm.windowMode() !== 'presented') {
|
|
359
|
+
state.pinnedLegacy = 'the window is not in presented mode after its write-down';
|
|
360
|
+
this.host.emitTrace({ type: 'presentation:failed', agentName: agent.name, occasion, error: state.pinnedLegacy, legacy: true });
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
if (wrote && occasion === 'rebase') {
|
|
364
|
+
// The trace's read is the trace's: a throw there leaves the window
|
|
365
|
+
// presented, as written down, and skips the trace.
|
|
366
|
+
let count = 0;
|
|
367
|
+
try {
|
|
368
|
+
count = bodyHeadCount(cm.getPresentedMessages());
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
console.error(`[presentation] ${agent.name}: its window was written down at ${occasion}, and reading it back for the trace failed (${describe(error)}); its window is presented, and the write-down isn't traced`);
|
|
372
|
+
}
|
|
373
|
+
if (count > 0)
|
|
374
|
+
this.host.emitTrace({ type: 'presentation:presented', agentName: agent.name, occasion, count });
|
|
375
|
+
}
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Take out of the agent's window the rows a write-down from the record
|
|
380
|
+
* took in that aren't its (decision 1): each row that carries an audience
|
|
381
|
+
* stamp and that bearingOf reads as elsewhere to the agent, such as
|
|
382
|
+
* another agent's own row, mail stamped for a co-resident, or a held row
|
|
383
|
+
* for a window that leaves held rows out. Each leaves its place as a plain
|
|
384
|
+
* retraction does, so it reads as arrived, and it stays elsewhere, so no
|
|
385
|
+
* later point presents it. Two kinds of row stay: an unstamped row, which
|
|
386
|
+
* a release before the stamp stored and the write-down migrates whole,
|
|
387
|
+
* and a shared derivation's inheritance (at or before its checkpoint),
|
|
388
|
+
* which is the child's history. Nothing is taken out for an agent the
|
|
389
|
+
* registry has no slot for. Run at every adopt and rebase, not only
|
|
390
|
+
* after a write-down this process made, so a window whose write-down
|
|
391
|
+
* landed and whose retraction didn't (a crash between the two records, a
|
|
392
|
+
* failed write) is repaired the next time it is taken on. Rows are
|
|
393
|
+
* weighed by the agent's relationships now, not by those they entered
|
|
394
|
+
* under, so a row the module presented is taken out too where it is
|
|
395
|
+
* elsewhere to the agent now: an observed agent's own row, presented
|
|
396
|
+
* through an observe edge the agent no longer has (a restart whose
|
|
397
|
+
* configuration dropped it). A reader fork's framing is passed over. Traced as
|
|
398
|
+
* presentation:retracted, reason `elsewhere`; a failure is traced as
|
|
399
|
+
* presentation:failed at `occasion`, and the rows stay in the window
|
|
400
|
+
* until the agent's next turn start or point at rest takes them out
|
|
401
|
+
* (`retractPending`, tried there until it succeeds), or the next adopt or
|
|
402
|
+
* rebase; a fresh turn start that can't take them out fails
|
|
403
|
+
* (presentAtStart). Never throws.
|
|
404
|
+
*/
|
|
405
|
+
retractForeign(agent, occasion) {
|
|
406
|
+
const cm = agent.getContextManager();
|
|
407
|
+
const state = this.adopted.get(cm);
|
|
408
|
+
try {
|
|
409
|
+
if (!this.presenting(agent))
|
|
410
|
+
return;
|
|
411
|
+
const foreign = this.foreignRows(agent);
|
|
412
|
+
if (foreign.length > 0) {
|
|
413
|
+
cm.retractRows(foreign);
|
|
414
|
+
this.memos.delete(cm);
|
|
415
|
+
this.host.emitTrace({ type: 'presentation:retracted', agentName: agent.name, messageIds: foreign, reason: 'elsewhere' });
|
|
416
|
+
}
|
|
417
|
+
if (state)
|
|
418
|
+
delete state.retractPending;
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
if (state)
|
|
422
|
+
state.retractPending = true;
|
|
423
|
+
console.error(`[presentation] ${agent.name}: taking the rows that aren't its out of its window at ${occasion} failed; they stay in it, and no turn of its starts, until its next turn start or point at rest takes them out:`, error);
|
|
424
|
+
this.host.emitTrace({ type: 'presentation:failed', agentName: agent.name, occasion, error: describe(error) });
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/** The rows of the agent's window retractForeign takes out, each body by its first shard; none for an agent the registry has no slot for. */
|
|
428
|
+
foreignRows(agent) {
|
|
429
|
+
const cm = agent.getContextManager();
|
|
430
|
+
const facts = readerFacts(this.host.registry, agent.name);
|
|
431
|
+
// An agent the registry doesn't know has no slot to weigh a row by.
|
|
432
|
+
if (facts.ownSlot === undefined)
|
|
433
|
+
return [];
|
|
434
|
+
const framed = this.framed.get(agent.name);
|
|
435
|
+
const window = cm.getPresentedMessages();
|
|
436
|
+
const foreign = [];
|
|
437
|
+
for (let i = 0; i < window.length; i++) {
|
|
438
|
+
const row = window[i];
|
|
439
|
+
// A body is taken out whole by its first shard.
|
|
440
|
+
if (i > 0 && continuesBody(window[i - 1], row))
|
|
441
|
+
continue;
|
|
442
|
+
if (audienceOf(row.metadata) === undefined || framed?.has(row.id))
|
|
443
|
+
continue;
|
|
444
|
+
if (facts.derivedAt !== undefined && row.sequence <= facts.derivedAt)
|
|
445
|
+
continue;
|
|
446
|
+
if (bearingOf(row, facts, this.slotOfRow(cm, facts, row.id)) === 'elsewhere')
|
|
447
|
+
foreign.push(row.id);
|
|
448
|
+
}
|
|
449
|
+
return foreign;
|
|
450
|
+
}
|
|
451
|
+
/** retractForeign again, where it failed before (`retractPending`). */
|
|
452
|
+
retryRetraction(agent, occasion) {
|
|
453
|
+
if (this.adopted.get(agent.getContextManager())?.retractPending)
|
|
454
|
+
this.retractForeign(agent, occasion);
|
|
455
|
+
}
|
|
456
|
+
/** Let an agent go: its sink off, and what the module kept for it (its memo, its framing, its held arrivals) dropped. */
|
|
457
|
+
forget(agent) {
|
|
458
|
+
agent.setOwnRowSink?.(undefined);
|
|
459
|
+
const cm = agent.getContextManager();
|
|
460
|
+
this.adopted.delete(cm);
|
|
461
|
+
this.memos.delete(cm);
|
|
462
|
+
this.failedOwn.delete(cm);
|
|
463
|
+
this.heldUntilStart.delete(cm);
|
|
464
|
+
this.slotReaders.delete(cm);
|
|
465
|
+
this.framed.delete(agent.name);
|
|
466
|
+
this.framedPending.delete(agent.name);
|
|
467
|
+
this.restWithheld.delete(agent.name);
|
|
468
|
+
this.generation++;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* For every live agent the module presents to and whose turn isn't alive:
|
|
472
|
+
* its own rows stored and not in its window (the own-row sweep, traced as
|
|
473
|
+
* presentation:repaired), then whatever it is owed at rest (presentAtEnd).
|
|
474
|
+
* Boot runs it once every agent is taken on and the stored rows are in,
|
|
475
|
+
* whether or not an adopt repaired anything. An agent in `restExcept` has
|
|
476
|
+
* its own rows swept and nothing else: the boot couldn't read what it is
|
|
477
|
+
* owed to ask the wakes its rows owe, so they stay out of its window until
|
|
478
|
+
* its next turn start, which no wake was asked for. Until then no point at
|
|
479
|
+
* rest presents its arrivals (a row stored for it while it is idle, a
|
|
480
|
+
* turn's end), only its own rows, so a later boot finds them out of its
|
|
481
|
+
* window and reads them again.
|
|
482
|
+
*/
|
|
483
|
+
repairAndRest(options = {}) {
|
|
484
|
+
for (const name of options.restExcept ?? [])
|
|
485
|
+
this.restWithheld.add(name);
|
|
486
|
+
for (const agent of this.host.liveAgents()) {
|
|
487
|
+
if (!this.presenting(agent) || this.host.turnAlive(agent.name))
|
|
488
|
+
continue;
|
|
489
|
+
this.repair(agent);
|
|
490
|
+
if (!options.restExcept?.has(agent.name))
|
|
491
|
+
this.presentAtRest(agent, 'at-rest');
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
/** Whether the module presents for this agent: taken on, its manager open and in presented mode. */
|
|
495
|
+
presenting(agent) {
|
|
496
|
+
const cm = agent.getContextManager();
|
|
497
|
+
const state = this.adopted.get(cm);
|
|
498
|
+
return !!state && !state.pinnedLegacy && !cm.isClosed() && cm.windowMode() === 'presented';
|
|
499
|
+
}
|
|
500
|
+
// --------------------------------------------------------------------------
|
|
501
|
+
// Presentation points
|
|
502
|
+
// --------------------------------------------------------------------------
|
|
503
|
+
/**
|
|
504
|
+
* Present the agent's own rows now, in the order given, those not in its
|
|
505
|
+
* window yet, even while the host is frozen; then tell the other readers
|
|
506
|
+
* of its slot (afterStore), whatever became of that: a presentation that
|
|
507
|
+
* threw, or an agent the module doesn't present for (a failed rebase
|
|
508
|
+
* leaves its sink on). The own-row sink calls it. It is for a row the
|
|
509
|
+
* agent's stream carries (its round, its results, a notice injected at its
|
|
510
|
+
* boundary), one written before its next compile or after its stream's
|
|
511
|
+
* last request, or one written while it has no turn alive: any other row
|
|
512
|
+
* would enter a live window its requests don't carry, so its writer holds
|
|
513
|
+
* it back (storeOwnRow's `present: false`) for the next point's sweep.
|
|
514
|
+
*
|
|
515
|
+
* An own row whose presentation here failed (a transient store error) is
|
|
516
|
+
* kept, and presented ahead of the rows given at the agent's next call,
|
|
517
|
+
* in record order, while it is still out of the window: its window's
|
|
518
|
+
* ordinals never move, so a row written after it and presented first
|
|
519
|
+
* would stay ahead of it for good (a round's results ahead of its reply,
|
|
520
|
+
* which every later compile would send so). It is kept until the agent's
|
|
521
|
+
* next turn start or tool boundary, whose own-row sweep presents it by
|
|
522
|
+
* that point's rule, or leaves it for the next point when that fails
|
|
523
|
+
* too: a request compiled, or a stream handed its rows, without it
|
|
524
|
+
* mustn't find it in the window ahead of a reply it didn't come before.
|
|
525
|
+
* A row its writer held back is never kept: it waits for the next point's
|
|
526
|
+
* sweep. Never throws.
|
|
527
|
+
*/
|
|
528
|
+
presentOwn(agent, ids) {
|
|
529
|
+
const cm = agent.getContextManager();
|
|
530
|
+
// Every row this call tries, the ones an earlier failed write kept
|
|
531
|
+
// among them, from before the first read: a read that throws keeps them
|
|
532
|
+
// all (keepFailedOwn), so none of them is put behind the rows written
|
|
533
|
+
// after it.
|
|
534
|
+
let tried = unique([...(this.failedOwn.get(cm) ?? []), ...ids]);
|
|
535
|
+
try {
|
|
536
|
+
if (this.presenting(agent)) {
|
|
537
|
+
tried = unique([...this.failedOwnOf(agent), ...ids]).filter((id) => cm.ordinalOf(id) === undefined);
|
|
538
|
+
this.present(agent, tried, 'write');
|
|
539
|
+
this.keepFailedOwn(agent, tried);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
catch (error) {
|
|
543
|
+
this.failed(agent, 'write', error);
|
|
544
|
+
this.keepFailedOwn(agent, tried);
|
|
545
|
+
}
|
|
546
|
+
this.afterStore(agent, ids);
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Rows `writer` just stored, in its own slot: each other reader of that
|
|
550
|
+
* slot (its owners and its observers) whose turn isn't alive, and the
|
|
551
|
+
* writer itself, is presented what it is owed at once, when one of the
|
|
552
|
+
* rows is its own or an arrival for it (present at rest). A reader with a
|
|
553
|
+
* turn alive takes them at its next point. Frozen, only own rows present.
|
|
554
|
+
* The module needn't present for the writer: one left in legacy mode
|
|
555
|
+
* tells the readers of its slot too. Each reader is weighed apart, so one
|
|
556
|
+
* whose window can't be read is traced and the others are still told.
|
|
557
|
+
* Never throws.
|
|
558
|
+
*/
|
|
559
|
+
afterStore(writer, ids) {
|
|
560
|
+
try {
|
|
561
|
+
const stored = unique(ids);
|
|
562
|
+
if (stored.length === 0)
|
|
563
|
+
return;
|
|
564
|
+
const writerCm = writer.getContextManager();
|
|
565
|
+
const rows = stored.map((id) => writerCm.getMessage(id)).filter((row) => row !== null);
|
|
566
|
+
const slot = this.host.registry.ownSlotOf(writer.name);
|
|
567
|
+
const readers = new Set([writer.name, ...(slot ? this.host.registry.readersOf(slot) : [])]);
|
|
568
|
+
for (const reader of [...this.host.liveAgents()]) {
|
|
569
|
+
if (!readers.has(reader.name))
|
|
570
|
+
continue;
|
|
571
|
+
try {
|
|
572
|
+
if (!this.presenting(reader) || this.host.turnAlive(reader.name))
|
|
573
|
+
continue;
|
|
574
|
+
const facts = readerFacts(this.host.registry, reader.name);
|
|
575
|
+
// The rows are in the writer's own slot: the reader's own when it
|
|
576
|
+
// is the writer or shares that slot (bearingOf compares the ref),
|
|
577
|
+
// else a slot it observes, weighed by its edges on that slot.
|
|
578
|
+
const from = reader === writer ? 'own' : slot;
|
|
579
|
+
// A row the writer's own slot doesn't hold (none found) can't be
|
|
580
|
+
// weighed here; the reader's own scan weighs it.
|
|
581
|
+
const owed = rows.length < stored.length || rows.some((row) => bearingOf(row, facts, from) !== 'elsewhere');
|
|
582
|
+
if (owed)
|
|
583
|
+
this.presentAtRest(reader, 'at-rest');
|
|
584
|
+
}
|
|
585
|
+
catch (error) {
|
|
586
|
+
this.failed(reader, 'at-rest', error);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
catch (error) {
|
|
591
|
+
this.failed(writer, 'at-rest', error);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* A turn's start, before its compile. `'turn-start'` (a fresh start) and
|
|
596
|
+
* `'assembly'` (the rows a wake assembled for it) present, in one call and
|
|
597
|
+
* record order, the agent's own rows not in its window and its arrivals
|
|
598
|
+
* (none while frozen); `'compile'`, the backstop before every compile,
|
|
599
|
+
* fresh or resumed, its own rows only. Returns the arrivals presented, one
|
|
600
|
+
* per body, for the start to weigh. A fresh start ends a rollback's hold
|
|
601
|
+
* on the arrivals its cut returned (holdUntilStart): they enter with the
|
|
602
|
+
* rest, in record order, and the hold ends once they have, so a start
|
|
603
|
+
* that fails keeps it, and so does one while frozen, which presents no
|
|
604
|
+
* arrival; so does an agent the boot's pass at rest left out
|
|
605
|
+
* (repairAndRest's `restExcept`). A fresh start's presentation that fails (a read
|
|
606
|
+
* of the window or the record that throws, or a write of the window that
|
|
607
|
+
* leaves a row out) is traced, and then throws, so the start fails rather
|
|
608
|
+
* than compile without what it was started for, which waits outside the
|
|
609
|
+
* window for the next point. So does a fresh start where rows a
|
|
610
|
+
* write-down took in that aren't the agent's are still in its window
|
|
611
|
+
* (retractForeign failed again here), rather than compile with them. The
|
|
612
|
+
* backstop's is traced, and its compile goes on.
|
|
613
|
+
*/
|
|
614
|
+
presentAtStart(agent, occasion) {
|
|
615
|
+
if (!this.presenting(agent))
|
|
616
|
+
return [];
|
|
617
|
+
const cm = agent.getContextManager();
|
|
618
|
+
const fresh = occasion !== 'compile';
|
|
619
|
+
// The sweep below decides the own rows a failed write left (presentOwn):
|
|
620
|
+
// the backstop's at once, as its compile goes on whatever it presents; a
|
|
621
|
+
// fresh start's once it hasn't thrown, so the next own row's write still
|
|
622
|
+
// puts them first after a start that presented nothing. Only those kept
|
|
623
|
+
// before it: a listener of its presentation, which runs inside it, may
|
|
624
|
+
// store an own row whose presentation fails, kept by that nested write.
|
|
625
|
+
if (!fresh)
|
|
626
|
+
this.failedOwn.delete(cm);
|
|
627
|
+
const keptBefore = fresh ? new Set(this.failedOwn.get(cm) ?? []) : undefined;
|
|
628
|
+
// A fresh start is where rows a write-down took in that aren't the
|
|
629
|
+
// agent's, whose retraction failed, leave before its compile, and a
|
|
630
|
+
// reader fork's framing whose presentation failed enters.
|
|
631
|
+
if (fresh) {
|
|
632
|
+
this.retryRetraction(agent, occasion);
|
|
633
|
+
if (this.adopted.get(cm)?.retractPending) {
|
|
634
|
+
throw new Error(`${agent.name}'s turn start could not take the rows that aren't its out of its window (at ${occasion}); they stay in it, and it doesn't compile, until a later point takes them out`);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
if (fresh && this.framedPending.has(agent.name))
|
|
638
|
+
this.presentFramed(agent);
|
|
639
|
+
// A rollback's returned arrivals enter here (holdUntilStart): the hold
|
|
640
|
+
// is left out of this presentation, and ends once it hasn't failed, at a
|
|
641
|
+
// start that could present arrivals (none while frozen).
|
|
642
|
+
const withArrivals = fresh && !this.host.frozen();
|
|
643
|
+
const { arrivals, failure } = this.presentPending(agent, occasion, withArrivals, false, fresh ? "the start fails, and what it didn't present waits for the next point" : undefined);
|
|
644
|
+
if (fresh && failure !== undefined) {
|
|
645
|
+
throw new Error(`${agent.name}'s turn start could not present what it is owed (at ${occasion}): ${describe(failure)}`, { cause: failure });
|
|
646
|
+
}
|
|
647
|
+
if (keptBefore) {
|
|
648
|
+
const keptMeanwhile = [...(this.failedOwn.get(cm) ?? [])].filter((id) => !keptBefore.has(id));
|
|
649
|
+
if (keptMeanwhile.length > 0)
|
|
650
|
+
this.failedOwn.set(cm, new Set(keptMeanwhile));
|
|
651
|
+
else
|
|
652
|
+
this.failedOwn.delete(cm);
|
|
653
|
+
}
|
|
654
|
+
if (withArrivals) {
|
|
655
|
+
this.heldUntilStart.delete(cm);
|
|
656
|
+
this.restWithheld.delete(agent.name);
|
|
657
|
+
}
|
|
658
|
+
return arrivals;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* A tool boundary. First the round's own rows on the wire (`wire`: its
|
|
662
|
+
* reply, its results, a clock row due), those not in the window yet, in
|
|
663
|
+
* order; if that fails, nothing more. Then, unless the host is frozen or
|
|
664
|
+
* the stream can't carry injected rows (`injects` false), the agent's own
|
|
665
|
+
* rows not in its window and its arrivals, by bodies in record order, up
|
|
666
|
+
* to the first body the stream can't carry (isInjectableMidTurn: a tool
|
|
667
|
+
* block, or a row named as the agent), presented in one call: each body
|
|
668
|
+
* whole, named by its first shard not in the window. Those are what the
|
|
669
|
+
* stream carries next (`heard`); the cut body and those after it wait for
|
|
670
|
+
* the turn's end. A presentation cut short hears only the bodies wholly in
|
|
671
|
+
* the window. A read of the window or the record that throws is traced as
|
|
672
|
+
* presentation:failed, and the stream hears nothing: what wasn't presented
|
|
673
|
+
* waits for the next point, and the hand-off that follows checks the
|
|
674
|
+
* window as it then stands. Never throws, so the boundary goes on to hand
|
|
675
|
+
* the stream its results.
|
|
676
|
+
*/
|
|
677
|
+
presentAtBoundary(agent, wire, options) {
|
|
678
|
+
try {
|
|
679
|
+
if (!this.presenting(agent))
|
|
680
|
+
return { heard: [] };
|
|
681
|
+
const cm = agent.getContextManager();
|
|
682
|
+
const missing = unique(wire).filter((id) => cm.ordinalOf(id) === undefined);
|
|
683
|
+
// The round's rows are presented here, in order, and the own rows a
|
|
684
|
+
// failed write left (presentOwn) are this boundary's to sweep, or the
|
|
685
|
+
// next point's: none is put ahead of the next round's reply.
|
|
686
|
+
this.failedOwn.delete(cm);
|
|
687
|
+
if (missing.length > 0 && this.present(agent, missing, 'tool-boundary').error !== undefined)
|
|
688
|
+
return { heard: [] };
|
|
689
|
+
if (this.host.frozen() || !options.injects)
|
|
690
|
+
return { heard: [] };
|
|
691
|
+
const bodies = this.pending(agent, true);
|
|
692
|
+
const cut = bodies.findIndex((body) => body.shards.some((shard) => !isInjectableMidTurn(shard, agent)));
|
|
693
|
+
const prefix = cut === -1 ? bodies : bodies.slice(0, cut);
|
|
694
|
+
const cutAt = cut === -1 ? undefined : bodies[cut].shards[0].id;
|
|
695
|
+
const { entered } = this.present(agent, prefix.map((body) => body.shards[0].id), 'tool-boundary', {
|
|
696
|
+
unstamped: prefix.filter((body) => body.unstamped).length,
|
|
697
|
+
...(cutAt !== undefined ? { cutAt } : {}),
|
|
698
|
+
}, prefix);
|
|
699
|
+
const heard = prefix
|
|
700
|
+
.filter((body) => entered.has(body.shards[0].id) && body.shards.every((shard) => cm.ordinalOf(shard.id) !== undefined))
|
|
701
|
+
.map((body) => {
|
|
702
|
+
const head = body.shards[0];
|
|
703
|
+
return {
|
|
704
|
+
id: head.id,
|
|
705
|
+
participant: head.participant,
|
|
706
|
+
content: asCompiled(body.shards),
|
|
707
|
+
...(head.metadata !== undefined ? { metadata: head.metadata } : {}),
|
|
708
|
+
};
|
|
709
|
+
});
|
|
710
|
+
return { heard, ...(cutAt !== undefined ? { cutAt } : {}) };
|
|
711
|
+
}
|
|
712
|
+
catch (error) {
|
|
713
|
+
this.failed(agent, 'tool-boundary', error);
|
|
714
|
+
return { heard: [] };
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* A turn's end, and an agent at rest: when its logical turn isn't alive,
|
|
719
|
+
* its own rows not in its window and its arrivals (none while frozen), in
|
|
720
|
+
* one call and record order. Nothing while the turn is alive. Idempotent.
|
|
721
|
+
* Returns the arrivals presented, one per body.
|
|
722
|
+
*/
|
|
723
|
+
presentAtEnd(agent) {
|
|
724
|
+
return this.presentAtRest(agent, 'turn-end');
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* The rows a rollback's branch cut took out of the agent's window and
|
|
728
|
+
* kept in the record (its `returnedToArrivals`, #291): they are arrivals
|
|
729
|
+
* again, and wait outside the window until the agent's next turn start
|
|
730
|
+
* (presentAtStart's `'turn-start'` or `'assembly'`), which presents them
|
|
731
|
+
* with whatever else it is owed, in record order (a start that fails
|
|
732
|
+
* keeps them held). Until then no point at
|
|
733
|
+
* rest presents them: not the surgery's release, a resume, a turn's end
|
|
734
|
+
* or a row stored for the agent while it is idle, so the cut the
|
|
735
|
+
* operator made is what the agent's window holds until it speaks again.
|
|
736
|
+
* A tool boundary isn't held back (a turn alive started since). An own
|
|
737
|
+
* row among them isn't held: own rows enter at every point, as ever. The
|
|
738
|
+
* hold is the branch's the store is on (the rollback's): on another
|
|
739
|
+
* branch the same stored rows are arrivals as any, so a later move of the
|
|
740
|
+
* store's branch, a suppression or rollback through another agent
|
|
741
|
+
* included, ends it there, and back on this one they are held again until
|
|
742
|
+
* the agent's next turn start. In memory: a restart presents them at its
|
|
743
|
+
* pass at rest, or, where it boots quiesced, at the resume (a frozen pass
|
|
744
|
+
* presents no arrival), and an agent let go (forget)
|
|
745
|
+
* drops them. Rows given again on the same branch add to those held, and
|
|
746
|
+
* on another replace them. Never throws.
|
|
747
|
+
*/
|
|
748
|
+
holdUntilStart(agent, ids) {
|
|
749
|
+
const named = unique(ids);
|
|
750
|
+
if (named.length === 0)
|
|
751
|
+
return;
|
|
752
|
+
const cm = agent.getContextManager();
|
|
753
|
+
try {
|
|
754
|
+
const branch = cm.currentBranch().name;
|
|
755
|
+
const held = this.heldUntilStart.get(cm);
|
|
756
|
+
const kept = held?.branch === branch ? held.ids : new Set();
|
|
757
|
+
for (const id of named)
|
|
758
|
+
kept.add(id);
|
|
759
|
+
this.heldUntilStart.set(cm, { branch, ids: kept });
|
|
760
|
+
// A scan at rest that left an earlier hold's rows out may have passed them.
|
|
761
|
+
this.memos.delete(cm);
|
|
762
|
+
}
|
|
763
|
+
catch (error) {
|
|
764
|
+
console.error(`[presentation] ${agent.name}: could not hold ${named.length} row(s) out of its window until its next turn start; they enter at its next presentation point:`, error);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* The check before a tool boundary hands the stream its rows: `sent` (the
|
|
769
|
+
* round's reply, its results and the injected rows, in the order the
|
|
770
|
+
* stream is given them) not yet in the window are presented, in order,
|
|
771
|
+
* and the window's newest bodies must then be exactly those rows. On a
|
|
772
|
+
* mismatch it traces presentation:hand-off-mismatch and the rows are not
|
|
773
|
+
* vouched for, so the stream's later thinking is left out at the next
|
|
774
|
+
* compile rather than sent under a prefix its window doesn't hold. An
|
|
775
|
+
* agent the module doesn't present for is vouched for as before. A read
|
|
776
|
+
* of the window that throws is traced as presentation:failed, and the
|
|
777
|
+
* rows are not vouched for: the check couldn't be made. Never throws.
|
|
778
|
+
*/
|
|
779
|
+
handOff(agent, sent) {
|
|
780
|
+
try {
|
|
781
|
+
if (!this.presenting(agent))
|
|
782
|
+
return { vouched: true };
|
|
783
|
+
const cm = agent.getContextManager();
|
|
784
|
+
const expected = unique(sent);
|
|
785
|
+
if (expected.length === 0)
|
|
786
|
+
return { vouched: true };
|
|
787
|
+
const missing = expected.filter((id) => cm.ordinalOf(id) === undefined);
|
|
788
|
+
if (missing.length > 0 && this.present(agent, missing, 'hand-off').error !== undefined) {
|
|
789
|
+
this.host.emitTrace({ type: 'presentation:hand-off-mismatch', agentName: agent.name, expected, tail: lastBodyHeads(cm.getPresentedMessages(), expected.length) });
|
|
790
|
+
return { vouched: false };
|
|
791
|
+
}
|
|
792
|
+
const tail = lastBodyHeads(cm.getPresentedMessages(), expected.length);
|
|
793
|
+
if (tail.length !== expected.length || tail.some((id, i) => id !== expected[i])) {
|
|
794
|
+
this.host.emitTrace({ type: 'presentation:hand-off-mismatch', agentName: agent.name, expected, tail });
|
|
795
|
+
return { vouched: false };
|
|
796
|
+
}
|
|
797
|
+
return { vouched: true };
|
|
798
|
+
}
|
|
799
|
+
catch (error) {
|
|
800
|
+
this.failed(agent, 'hand-off', error);
|
|
801
|
+
return { vouched: false };
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
/** A read of the agent's arrivals not yet presented, one per body, in record order (for previews and a release). */
|
|
805
|
+
arrivalsOf(agent) {
|
|
806
|
+
if (!this.presenting(agent))
|
|
807
|
+
return [];
|
|
808
|
+
return this.pending(agent, true).filter((body) => body.bearing === 'arrival').map((body) => body.shards[0]);
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* A reader fork was handed held rows as framing (AF-5): those not in its
|
|
812
|
+
* window yet are presented there, when the module presents for it, and
|
|
813
|
+
* the fork counts as holding every one of them (holders, `'framing'`),
|
|
814
|
+
* however its window filters held rows, until it is let go (forget). A
|
|
815
|
+
* fork framed before it is taken on has them presented at its adopt. Its
|
|
816
|
+
* window's filter (the residents', which a reader fork inherits) keeps
|
|
817
|
+
* them out of its compiles, as it keeps all held traffic; its framing row
|
|
818
|
+
* shows them where the framing puts them (the default's does); nothing
|
|
819
|
+
* else of its window moves. Rows whose presentation fails enter at the
|
|
820
|
+
* fork's next turn start, or after a move of the store's branch (rebase).
|
|
821
|
+
*/
|
|
822
|
+
noteFramed(agent, rowIds) {
|
|
823
|
+
const set = this.framed.get(agent.name) ?? new Set();
|
|
824
|
+
for (const id of rowIds)
|
|
825
|
+
set.add(id);
|
|
826
|
+
this.framed.set(agent.name, set);
|
|
827
|
+
this.framedPending.add(agent.name);
|
|
828
|
+
this.presentFramed(agent);
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Drop the rows a reader fork was handed as framing (noteFramed) when the
|
|
832
|
+
* fork is never taken on, so never let go (forget): one its run refused
|
|
833
|
+
* before registering it (the host quiesced or stopping, a surgery holding
|
|
834
|
+
* the store). Nothing for a name handed none.
|
|
835
|
+
*/
|
|
836
|
+
forgetFramed(agent) {
|
|
837
|
+
this.framed.delete(agent);
|
|
838
|
+
this.framedPending.delete(agent);
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* The rows the agent is owed and its window doesn't hold yet, one per
|
|
842
|
+
* body (its first shard not in the window), in record order: its own rows
|
|
843
|
+
* and its arrivals, frozen or not, whatever its turn. None for an agent the
|
|
844
|
+
* module doesn't present for. The subconscious's window start is fixed
|
|
845
|
+
* once no row it is owed stored before the start is among them.
|
|
846
|
+
*/
|
|
847
|
+
owedOf(agent) {
|
|
848
|
+
if (!this.presenting(agent))
|
|
849
|
+
return [];
|
|
850
|
+
return this.pending(agent, true).map((body) => body.shards[0]);
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Whether a row just stored in the agent's own slot, and presented at
|
|
854
|
+
* rest by afterStore if at all, is out of its window though the agent is
|
|
855
|
+
* owed it (its own row, or an arrival): a presentation that failed, or
|
|
856
|
+
* the host's freeze, kept it out. A row that is nothing to the agent (held
|
|
857
|
+
* from a resident, stamped for another) isn't. Yes where the window can't
|
|
858
|
+
* be read to tell; no for an agent the module doesn't present for.
|
|
859
|
+
*/
|
|
860
|
+
notPresented(agent, id) {
|
|
861
|
+
try {
|
|
862
|
+
if (!this.presenting(agent))
|
|
863
|
+
return false;
|
|
864
|
+
const cm = agent.getContextManager();
|
|
865
|
+
if (cm.windowStatusOf(id) !== 'out')
|
|
866
|
+
return false;
|
|
867
|
+
const row = cm.getMessage(id);
|
|
868
|
+
return row === null || bearingOf(row, readerFacts(this.host.registry, agent.name), 'own') !== 'elsewhere';
|
|
869
|
+
}
|
|
870
|
+
catch {
|
|
871
|
+
return true;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
// --------------------------------------------------------------------------
|
|
875
|
+
// Reads
|
|
876
|
+
// --------------------------------------------------------------------------
|
|
877
|
+
/**
|
|
878
|
+
* Every live agent holding a row: each whose window has it at a place,
|
|
879
|
+
* leaving out one whose window leaves held rows out when the row is held
|
|
880
|
+
* (a window written down from the record took in those an earlier release
|
|
881
|
+
* stored, before the audience stamp; retractForeign takes out the rest),
|
|
882
|
+
* and every one when a suppress rule hid the row, which no view renders;
|
|
883
|
+
* each whose window
|
|
884
|
+
* is still the whole record and whose record holds it; and each reader fork
|
|
885
|
+
* handed it as framing.
|
|
886
|
+
*/
|
|
887
|
+
holders(rowId) {
|
|
888
|
+
const holders = [];
|
|
889
|
+
const inWindow = new Set();
|
|
890
|
+
for (const agent of this.host.liveAgents()) {
|
|
891
|
+
const cm = agent.getContextManager();
|
|
892
|
+
if (cm.isClosed())
|
|
893
|
+
continue;
|
|
894
|
+
if (this.adopted.get(cm)?.pinnedLegacy || cm.windowMode() !== 'presented') {
|
|
895
|
+
if (cm.getMessage(rowId) !== null || cm.getPresentedMessages().some((m) => m.id === rowId)) {
|
|
896
|
+
holders.push({ agent: agent.name, via: 'legacy' });
|
|
897
|
+
}
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
const position = positionIn(cm, rowId);
|
|
901
|
+
if (position === undefined)
|
|
902
|
+
continue;
|
|
903
|
+
const metadata = cm.getMessage(rowId)?.metadata;
|
|
904
|
+
if (suppressedRow(metadata))
|
|
905
|
+
continue;
|
|
906
|
+
if (readerFacts(this.host.registry, agent.name).hidesHeld && heldFromResidents(metadata))
|
|
907
|
+
continue;
|
|
908
|
+
holders.push({ agent: agent.name, via: 'window', position });
|
|
909
|
+
inWindow.add(agent.name);
|
|
910
|
+
}
|
|
911
|
+
for (const [agent, ids] of this.framed) {
|
|
912
|
+
if (ids.has(rowId) && !inWindow.has(agent))
|
|
913
|
+
holders.push({ agent, via: 'framing' });
|
|
914
|
+
}
|
|
915
|
+
return holders;
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Rows removed from the record through `remover` (a context manager of a
|
|
919
|
+
* live agent's, or one opened apart from them), taken out of every other
|
|
920
|
+
* live agent's window that still holds them: context-manager tombstones a
|
|
921
|
+
* removal in the remover's own window only, and another window on the
|
|
922
|
+
* same slot, or merging it, keeps the row's place (CM-T) while its listing
|
|
923
|
+
* and its compile leave the row out. Each row leaves at its own place, so
|
|
924
|
+
* no other row's place moves, and a cursor or watermark kept by place
|
|
925
|
+
* stays good. A window on a branch whose record still holds the row (a
|
|
926
|
+
* fork, a derived agent's view made before the removal) keeps it, as does
|
|
927
|
+
* a window that is still the record, which is never written to here
|
|
928
|
+
* (context-manager's retractRemoved passes over both). Each window's rows
|
|
929
|
+
* taken out are traced (presentation:retracted, with `reason`), and a
|
|
930
|
+
* window that couldn't be repaired is logged and passed by: the removal
|
|
931
|
+
* stands. `ids` names every row removed, a body by each of its shards.
|
|
932
|
+
* Returns what left each window. Never throws.
|
|
933
|
+
*/
|
|
934
|
+
retractRemoved(remover, ids, reason) {
|
|
935
|
+
const named = unique(ids).filter((id) => id !== '');
|
|
936
|
+
if (named.length === 0)
|
|
937
|
+
return [];
|
|
938
|
+
const visited = new Set(remover ? [remover] : []);
|
|
939
|
+
const out = [];
|
|
940
|
+
for (const agent of this.host.liveAgents()) {
|
|
941
|
+
const cm = agent.getContextManager();
|
|
942
|
+
if (visited.has(cm))
|
|
943
|
+
continue;
|
|
944
|
+
visited.add(cm);
|
|
945
|
+
try {
|
|
946
|
+
// A window that is still the record holds no place for the row:
|
|
947
|
+
// checked here as well as by the call, which writes nothing then.
|
|
948
|
+
if (cm.isClosed() || cm.windowMode() !== 'presented')
|
|
949
|
+
continue;
|
|
950
|
+
const taken = cm.retractRemoved(named);
|
|
951
|
+
if (taken.length === 0)
|
|
952
|
+
continue;
|
|
953
|
+
out.push({ agent: agent.name, ids: taken });
|
|
954
|
+
this.host.emitTrace({ type: 'presentation:retracted', agentName: agent.name, messageIds: taken, reason });
|
|
955
|
+
}
|
|
956
|
+
catch (error) {
|
|
957
|
+
console.error(`[presentation] could not take ${named.length} removed row(s) out of ${agent.name}'s window (${reason}); the removal stands: ${describe(error)}`);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return out;
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* At boot, after every live agent is taken on: take out of each window
|
|
964
|
+
* the module presents for every row it holds that its record no longer
|
|
965
|
+
* does (context-manager's retractAbsentRows), as retractRemoved takes out
|
|
966
|
+
* the rows a live removal named. These are rows removed where no live
|
|
967
|
+
* context heard it: by an offline recovery's suppressions, with the host
|
|
968
|
+
* stopped, through the one context it opened, and removals whose own
|
|
969
|
+
* tombstone failed. Traced as presentation:retracted with `boot`; a
|
|
970
|
+
* window that couldn't be repaired is logged and passed by. Each agent's
|
|
971
|
+
* context is open with every slot it merges by then, so a row of a slot
|
|
972
|
+
* it merges isn't taken for removed. Never throws.
|
|
973
|
+
*/
|
|
974
|
+
retractAbsentRows() {
|
|
975
|
+
const out = [];
|
|
976
|
+
const visited = new Set();
|
|
977
|
+
for (const agent of this.host.liveAgents()) {
|
|
978
|
+
const cm = agent.getContextManager();
|
|
979
|
+
if (visited.has(cm))
|
|
980
|
+
continue;
|
|
981
|
+
visited.add(cm);
|
|
982
|
+
try {
|
|
983
|
+
if (!this.presenting(agent))
|
|
984
|
+
continue;
|
|
985
|
+
const taken = cm.retractAbsentRows();
|
|
986
|
+
if (taken.length === 0)
|
|
987
|
+
continue;
|
|
988
|
+
out.push({ agent: agent.name, ids: taken });
|
|
989
|
+
this.host.emitTrace({ type: 'presentation:retracted', agentName: agent.name, messageIds: taken, reason: 'boot' });
|
|
990
|
+
}
|
|
991
|
+
catch (error) {
|
|
992
|
+
console.error(`[presentation] could not take removed rows out of ${agent.name}'s window at boot: ${describe(error)}`);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return out;
|
|
996
|
+
}
|
|
997
|
+
/** Whether a reader fork was handed the row as framing (noteFramed), and
|
|
998
|
+
* hasn't been let go since, whether or not its window holds the row. */
|
|
999
|
+
framedToAny(rowId) {
|
|
1000
|
+
for (const ids of this.framed.values())
|
|
1001
|
+
if (ids.has(rowId))
|
|
1002
|
+
return true;
|
|
1003
|
+
return false;
|
|
1004
|
+
}
|
|
1005
|
+
/** Whether any live agent but `except` holds the row in a window or as framing: a window that is still the record doesn't count. */
|
|
1006
|
+
presentedToAnyone(rowId, options = {}) {
|
|
1007
|
+
return this.holders(rowId).some((h) => h.via !== 'legacy' && h.agent !== options.except);
|
|
1008
|
+
}
|
|
1009
|
+
/** The rows of the agent's window at or after `mark`, in window order; none in legacy mode. */
|
|
1010
|
+
presentedSince(agent, mark) {
|
|
1011
|
+
const cm = agent.getContextManager();
|
|
1012
|
+
if (cm.windowMode() !== 'presented')
|
|
1013
|
+
return [];
|
|
1014
|
+
return cm.getPresentedMessages().filter((m) => (cm.ordinalOf(m.id) ?? -1) >= mark);
|
|
1015
|
+
}
|
|
1016
|
+
/** Hear each presentation into the named agent's window, after its rows entered. Returns the unsubscribe. */
|
|
1017
|
+
onPresented(agentName, listener) {
|
|
1018
|
+
const set = this.listeners.get(agentName) ?? new Set();
|
|
1019
|
+
set.add(listener);
|
|
1020
|
+
this.listeners.set(agentName, set);
|
|
1021
|
+
return () => {
|
|
1022
|
+
set.delete(listener);
|
|
1023
|
+
if (set.size === 0 && this.listeners.get(agentName) === set)
|
|
1024
|
+
this.listeners.delete(agentName);
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* A write's stamp, checked as it is stored through `deliveredTo`'s
|
|
1029
|
+
* context: an audience none of whose agents reads that slot is traced
|
|
1030
|
+
* (presentation:unreadable-audience), once for the write. The row is
|
|
1031
|
+
* stored as named.
|
|
1032
|
+
*/
|
|
1033
|
+
checkAudience(metadata, deliveredTo) {
|
|
1034
|
+
const stamp = audienceOf(metadata);
|
|
1035
|
+
if (!stamp)
|
|
1036
|
+
return;
|
|
1037
|
+
const slot = this.host.registry.ownSlotOf(deliveredTo);
|
|
1038
|
+
if (!slot || !audienceFault(stamp.audience, slot, this.host.registry))
|
|
1039
|
+
return;
|
|
1040
|
+
this.host.emitTrace({ type: 'presentation:unreadable-audience', audience: stamp.audience, slot, deliveredTo });
|
|
1041
|
+
}
|
|
1042
|
+
// --------------------------------------------------------------------------
|
|
1043
|
+
// Internals
|
|
1044
|
+
// --------------------------------------------------------------------------
|
|
1045
|
+
/**
|
|
1046
|
+
* The own rows whose presentation at their write failed (presentOwn) and
|
|
1047
|
+
* that are still out of the agent's window, in record order. A row that
|
|
1048
|
+
* entered by another point since, was undone, or left the record (a
|
|
1049
|
+
* branch move, a removal) is dropped.
|
|
1050
|
+
*/
|
|
1051
|
+
failedOwnOf(agent) {
|
|
1052
|
+
const cm = agent.getContextManager();
|
|
1053
|
+
const kept = this.failedOwn.get(cm);
|
|
1054
|
+
if (!kept || kept.size === 0)
|
|
1055
|
+
return [];
|
|
1056
|
+
const owed = [];
|
|
1057
|
+
for (const id of kept) {
|
|
1058
|
+
const row = cm.windowStatusOf(id) === 'out' ? cm.getMessage(id) : null;
|
|
1059
|
+
if (row)
|
|
1060
|
+
owed.push({ id, sequence: row.sequence });
|
|
1061
|
+
}
|
|
1062
|
+
if (owed.length === 0)
|
|
1063
|
+
this.failedOwn.delete(cm);
|
|
1064
|
+
return owed.sort((a, b) => a.sequence - b.sequence).map((row) => row.id);
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Keep, of the own rows presentOwn tried, those still out of the window,
|
|
1068
|
+
* for its next call; forget the rest. Rows kept meanwhile that this call
|
|
1069
|
+
* didn't try stay kept: a listener of its presentation, which runs
|
|
1070
|
+
* inside it, may have stored an own row whose presentation failed, kept
|
|
1071
|
+
* by that nested call.
|
|
1072
|
+
*/
|
|
1073
|
+
keepFailedOwn(agent, tried) {
|
|
1074
|
+
const cm = agent.getContextManager();
|
|
1075
|
+
const triedHere = new Set(tried);
|
|
1076
|
+
const keptMeanwhile = [...(this.failedOwn.get(cm) ?? [])].filter((id) => !triedHere.has(id));
|
|
1077
|
+
let keep;
|
|
1078
|
+
try {
|
|
1079
|
+
keep = [...tried, ...keptMeanwhile].filter((id) => cm.ordinalOf(id) === undefined);
|
|
1080
|
+
}
|
|
1081
|
+
catch {
|
|
1082
|
+
// A window that can't be read now: keep every row tried.
|
|
1083
|
+
keep = [...tried, ...keptMeanwhile];
|
|
1084
|
+
}
|
|
1085
|
+
if (keep.length > 0)
|
|
1086
|
+
this.failedOwn.set(cm, new Set(keep));
|
|
1087
|
+
else
|
|
1088
|
+
this.failedOwn.delete(cm);
|
|
1089
|
+
}
|
|
1090
|
+
/** presentAtEnd under a named occasion; a rollback's held arrivals wait (holdUntilStart), and so do all of an agent's the boot left out (restWithheld). */
|
|
1091
|
+
presentAtRest(agent, occasion) {
|
|
1092
|
+
if (!this.presenting(agent) || this.host.turnAlive(agent.name))
|
|
1093
|
+
return [];
|
|
1094
|
+
this.retryRetraction(agent, occasion);
|
|
1095
|
+
return this.presentPending(agent, occasion, !this.host.frozen() && !this.restWithheld.has(agent.name), true).arrivals;
|
|
1096
|
+
}
|
|
1097
|
+
/** The rows the agent was handed as framing (noteFramed) that its window doesn't hold yet, presented in the order handed. */
|
|
1098
|
+
presentFramed(agent) {
|
|
1099
|
+
const framed = this.framed.get(agent.name);
|
|
1100
|
+
if (!framed || framed.size === 0) {
|
|
1101
|
+
this.framedPending.delete(agent.name);
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
try {
|
|
1105
|
+
if (!this.presenting(agent))
|
|
1106
|
+
return;
|
|
1107
|
+
const cm = agent.getContextManager();
|
|
1108
|
+
// presentRows refuses a row the window holds already, and a reader
|
|
1109
|
+
// fork derived from a presented window holds those rows its parent's
|
|
1110
|
+
// window was written down with.
|
|
1111
|
+
const unpresented = new Set(cm.getUnpresentedMessages().map((m) => m.id));
|
|
1112
|
+
const { error } = this.present(agent, [...framed].filter((id) => unpresented.has(id)), 'framing');
|
|
1113
|
+
// Pending until a presentation of them doesn't fail: the fork's next
|
|
1114
|
+
// turn start tries again (presentAtStart).
|
|
1115
|
+
if (error === undefined)
|
|
1116
|
+
this.framedPending.delete(agent.name);
|
|
1117
|
+
}
|
|
1118
|
+
catch (error) {
|
|
1119
|
+
this.failed(agent, 'framing', error);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
/** The own-row sweep: the agent's own rows not in its window, presented in record order, traced as a repair. */
|
|
1123
|
+
repair(agent, occasion = 'adopt') {
|
|
1124
|
+
try {
|
|
1125
|
+
const own = this.pending(agent, false);
|
|
1126
|
+
if (own.length === 0)
|
|
1127
|
+
return;
|
|
1128
|
+
const { entered } = this.present(agent, own.map((body) => body.shards[0].id), occasion, undefined, own, true);
|
|
1129
|
+
if (entered.size > 0)
|
|
1130
|
+
this.host.emitTrace({ type: 'presentation:repaired', agentName: agent.name, count: entered.size });
|
|
1131
|
+
}
|
|
1132
|
+
catch (error) {
|
|
1133
|
+
this.failed(agent, occasion, error);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Present the agent's own pending rows, and its arrivals when `arrivals`
|
|
1138
|
+
* (but those a rollback holds for its next start, with `atRest`), in one
|
|
1139
|
+
* call; return the arrivals that entered whole, and the error when the
|
|
1140
|
+
* presentation failed: a read that threw, or a write that left a row it
|
|
1141
|
+
* named out. `next` is what the failure's log says becomes of its rows.
|
|
1142
|
+
*/
|
|
1143
|
+
presentPending(agent, occasion, arrivals, atRest = false, next) {
|
|
1144
|
+
try {
|
|
1145
|
+
const cm = agent.getContextManager();
|
|
1146
|
+
const bodies = this.pending(agent, arrivals, atRest);
|
|
1147
|
+
if (bodies.length === 0)
|
|
1148
|
+
return { arrivals: [] };
|
|
1149
|
+
const heads = bodies.map((body) => body.shards[0].id);
|
|
1150
|
+
const { entered, error } = this.present(agent, heads, occasion, {
|
|
1151
|
+
unstamped: bodies.filter((body) => body.unstamped).length,
|
|
1152
|
+
...(next !== undefined ? { next } : {}),
|
|
1153
|
+
}, bodies);
|
|
1154
|
+
return {
|
|
1155
|
+
arrivals: bodies
|
|
1156
|
+
.filter((body) => body.bearing === 'arrival' && entered.has(body.shards[0].id)
|
|
1157
|
+
&& body.shards.every((shard) => cm.ordinalOf(shard.id) !== undefined))
|
|
1158
|
+
.map((body) => body.shards[0]),
|
|
1159
|
+
...(error !== undefined && heads.some((id) => !entered.has(id)) ? { failure: error } : {}),
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
catch (error) {
|
|
1163
|
+
this.failed(agent, occasion, error, undefined, next);
|
|
1164
|
+
return { arrivals: [], failure: error };
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* The agent's pending bodies, in record order: its own rows not in its
|
|
1169
|
+
* window, and its arrivals when `arrivals`, leaving out, with `atRest`,
|
|
1170
|
+
* an arrival a rollback holds for the agent's next start (any shard of
|
|
1171
|
+
* its body held). A body is its run of unpresented shards
|
|
1172
|
+
* (continuesBody), weighed by its first.
|
|
1173
|
+
*/
|
|
1174
|
+
pending(agent, arrivals, atRest = false) {
|
|
1175
|
+
const cm = agent.getContextManager();
|
|
1176
|
+
const facts = readerFacts(this.host.registry, agent.name);
|
|
1177
|
+
const factsKey = JSON.stringify(facts);
|
|
1178
|
+
const branch = cm.currentBranch().name;
|
|
1179
|
+
// A rollback's hold is its branch's: on another the rows are arrivals as any.
|
|
1180
|
+
const hold = atRest ? this.heldUntilStart.get(cm) : undefined;
|
|
1181
|
+
const held = hold?.branch === branch && hold.ids.size > 0 ? hold.ids : undefined;
|
|
1182
|
+
const length = cm.indexLength();
|
|
1183
|
+
let memo = this.memos.get(cm);
|
|
1184
|
+
if (!memo || memo.generation !== this.generation || memo.branch !== branch || memo.length !== length || memo.facts !== factsKey) {
|
|
1185
|
+
memo = { generation: this.generation, branch, length, facts: factsKey, scanFrom: -Infinity };
|
|
1186
|
+
this.memos.set(cm, memo);
|
|
1187
|
+
}
|
|
1188
|
+
// Every unpresented row at or below the memo's mark was elsewhere, so the
|
|
1189
|
+
// listing starts after it (context-manager finds the place by a search):
|
|
1190
|
+
// a scan walks the rows stored since, not every row of others' the
|
|
1191
|
+
// record holds. A scan that leaves held arrivals out may start at its own
|
|
1192
|
+
// mark too, which held rows don't hold back (heldScanFrom).
|
|
1193
|
+
const from = held ? Math.max(memo.scanFrom, memo.heldScanFrom ?? -Infinity) : memo.scanFrom;
|
|
1194
|
+
const unpresented = from === -Infinity
|
|
1195
|
+
? cm.getUnpresentedMessages()
|
|
1196
|
+
: cm.getUnpresentedMessages({ afterSequence: from });
|
|
1197
|
+
const bodies = [];
|
|
1198
|
+
let firstOwed;
|
|
1199
|
+
let newest = from;
|
|
1200
|
+
let i = 0;
|
|
1201
|
+
while (i < unpresented.length) {
|
|
1202
|
+
const start = i;
|
|
1203
|
+
i++;
|
|
1204
|
+
while (i < unpresented.length && continuesBody(unpresented[i - 1], unpresented[i]))
|
|
1205
|
+
i++;
|
|
1206
|
+
const head = unpresented[start];
|
|
1207
|
+
newest = Math.max(newest, unpresented[i - 1].sequence);
|
|
1208
|
+
if (head.sequence <= from)
|
|
1209
|
+
continue;
|
|
1210
|
+
const bearing = bearingOf(head, facts, this.slotOfRow(cm, facts, head.id));
|
|
1211
|
+
if (bearing === 'elsewhere')
|
|
1212
|
+
continue;
|
|
1213
|
+
// A held arrival is still owed, and every scan but this one counts it
|
|
1214
|
+
// (firstOwed, in `scanFrom`), so a scan that presents it, or counts
|
|
1215
|
+
// what is owed, starts at or before it; this one passes it over.
|
|
1216
|
+
const isHeld = bearing === 'arrival' && held !== undefined
|
|
1217
|
+
&& unpresented.slice(start, i).some((shard) => held.has(shard.id));
|
|
1218
|
+
if (!isHeld)
|
|
1219
|
+
firstOwed ??= head.sequence;
|
|
1220
|
+
if (isHeld || (bearing === 'arrival' && !arrivals))
|
|
1221
|
+
continue;
|
|
1222
|
+
bodies.push({ shards: unpresented.slice(start, i), bearing, unstamped: audienceOf(head.metadata) === undefined });
|
|
1223
|
+
}
|
|
1224
|
+
const next = firstOwed !== undefined ? Math.max(from, firstOwed - 1) : newest;
|
|
1225
|
+
if (held)
|
|
1226
|
+
memo.heldScanFrom = next;
|
|
1227
|
+
else
|
|
1228
|
+
memo.scanFrom = next;
|
|
1229
|
+
return bodies;
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* The slot of `cm`'s record a row is stored in, for bearingOf: its own
|
|
1233
|
+
* (`'own'`, which getMessage reads), else the merged slot that holds it
|
|
1234
|
+
* (mergedSlotsOf). The record holds the context's own slot and one
|
|
1235
|
+
* auxiliary view per merged slot, so a row not in its own is in the one
|
|
1236
|
+
* merged slot when there is one; when there are more, each is asked, by
|
|
1237
|
+
* a read-only handle on it over the manager's store, which follows its
|
|
1238
|
+
* branch and its writes as the manager's own views do. undefined for a
|
|
1239
|
+
* row in none of them (a slot the reader has no edge on).
|
|
1240
|
+
*/
|
|
1241
|
+
slotOfRow(cm, facts, id) {
|
|
1242
|
+
if (cm.getMessage(id) !== null)
|
|
1243
|
+
return 'own';
|
|
1244
|
+
const merged = mergedSlotsOf(facts);
|
|
1245
|
+
if (merged.length <= 1)
|
|
1246
|
+
return merged[0];
|
|
1247
|
+
let readers = this.slotReaders.get(cm);
|
|
1248
|
+
if (!readers) {
|
|
1249
|
+
readers = new Map();
|
|
1250
|
+
this.slotReaders.set(cm, readers);
|
|
1251
|
+
}
|
|
1252
|
+
for (const slot of merged) {
|
|
1253
|
+
let reader = readers.get(slot.namespace);
|
|
1254
|
+
if (!reader) {
|
|
1255
|
+
reader = new MessageStore(cm.getStore(), slot.namespace === null ? {} : { namespace: slot.namespace });
|
|
1256
|
+
readers.set(slot.namespace, reader);
|
|
1257
|
+
}
|
|
1258
|
+
if (reader.sequenceOf(id) !== undefined)
|
|
1259
|
+
return slot;
|
|
1260
|
+
}
|
|
1261
|
+
return undefined;
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* One presentRows call for `ids` in the agent's window: every row named
|
|
1265
|
+
* counts once it returns (it refuses a row already in the window, and
|
|
1266
|
+
* returns one ordinal per row named); on a throw, the rows that entered
|
|
1267
|
+
* before it count, as far as the window can be read then, and the
|
|
1268
|
+
* failure is traced. Traces what entered and tells the listeners.
|
|
1269
|
+
*/
|
|
1270
|
+
present(agent, ids, occasion, extra, bodies, quiet = false) {
|
|
1271
|
+
if (ids.length === 0)
|
|
1272
|
+
return { entered: new Set() };
|
|
1273
|
+
const cm = agent.getContextManager();
|
|
1274
|
+
const lengthBefore = cm.indexLength();
|
|
1275
|
+
let error;
|
|
1276
|
+
let entered;
|
|
1277
|
+
try {
|
|
1278
|
+
cm.presentRows(ids);
|
|
1279
|
+
// Read from the write's return, not the window: a read that throws
|
|
1280
|
+
// now mustn't keep rows the store holds from being traced and told.
|
|
1281
|
+
entered = new Set(ids);
|
|
1282
|
+
}
|
|
1283
|
+
catch (thrown) {
|
|
1284
|
+
error = thrown;
|
|
1285
|
+
try {
|
|
1286
|
+
entered = new Set(ids.filter((id) => cm.ordinalOf(id) !== undefined));
|
|
1287
|
+
}
|
|
1288
|
+
catch (readError) {
|
|
1289
|
+
console.error(`[presentation] ${agent.name}'s window couldn't be read after a failed presentation, so none of it is counted as entered:`, readError);
|
|
1290
|
+
entered = new Set();
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
const memo = this.memos.get(cm);
|
|
1294
|
+
if (memo && memo.length === lengthBefore) {
|
|
1295
|
+
try {
|
|
1296
|
+
memo.length = cm.indexLength();
|
|
1297
|
+
}
|
|
1298
|
+
catch {
|
|
1299
|
+
// The scan's memo can't be moved past the rows: the next scan reads them again.
|
|
1300
|
+
this.memos.delete(cm);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
if (error !== undefined)
|
|
1304
|
+
this.failed(agent, occasion, error, ids.filter((id) => !entered.has(id)), extra?.next);
|
|
1305
|
+
if (entered.size > 0) {
|
|
1306
|
+
if (!quiet) {
|
|
1307
|
+
const unstamped = extra?.unstamped !== undefined && bodies
|
|
1308
|
+
? bodies.filter((body) => body.unstamped && entered.has(body.shards[0].id)).length
|
|
1309
|
+
: 0;
|
|
1310
|
+
this.host.emitTrace({
|
|
1311
|
+
type: 'presentation:presented',
|
|
1312
|
+
agentName: agent.name,
|
|
1313
|
+
occasion,
|
|
1314
|
+
count: entered.size,
|
|
1315
|
+
...(extra?.cutAt !== undefined ? { cutAt: extra.cutAt } : {}),
|
|
1316
|
+
...(unstamped > 0 ? { unstamped } : {}),
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
this.tell(agent, occasion, ids, entered, bodies);
|
|
1320
|
+
}
|
|
1321
|
+
return { entered, ...(error !== undefined ? { error } : {}) };
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* Tell the agent's listeners of the rows that entered its window, in
|
|
1325
|
+
* order, each body as the window holds it. Where the window can't be
|
|
1326
|
+
* read for that, they are told of each entered row by its first shard
|
|
1327
|
+
* (a body's later shards left out), and that is logged: the rows are in
|
|
1328
|
+
* the window whatever the read, so the listeners (noteArrival, the
|
|
1329
|
+
* subconscious's anchor) still hear them.
|
|
1330
|
+
*/
|
|
1331
|
+
tell(agent, occasion, ids, entered, bodies) {
|
|
1332
|
+
const listeners = this.listeners.get(agent.name);
|
|
1333
|
+
if (!listeners || listeners.size === 0)
|
|
1334
|
+
return;
|
|
1335
|
+
const cm = agent.getContextManager();
|
|
1336
|
+
const byHead = new Map((bodies ?? []).map((body) => [body.shards[0].id, body.shards]));
|
|
1337
|
+
let window;
|
|
1338
|
+
const named = ids.filter((id) => entered.has(id));
|
|
1339
|
+
let rows;
|
|
1340
|
+
try {
|
|
1341
|
+
rows = named.flatMap((id) => {
|
|
1342
|
+
const shards = byHead.get(id);
|
|
1343
|
+
if (shards)
|
|
1344
|
+
return shards.filter((shard) => cm.ordinalOf(shard.id) !== undefined);
|
|
1345
|
+
// A row named alone (an own row at its write): its body as the window now holds it.
|
|
1346
|
+
window ??= cm.getPresentedMessages();
|
|
1347
|
+
const at = window.findIndex((m) => m.id === id);
|
|
1348
|
+
if (at === -1)
|
|
1349
|
+
return [];
|
|
1350
|
+
let end = at + 1;
|
|
1351
|
+
while (end < window.length && continuesBody(window[end - 1], window[end]))
|
|
1352
|
+
end++;
|
|
1353
|
+
return window.slice(at, end);
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
catch (error) {
|
|
1357
|
+
console.error(`[presentation] ${agent.name}'s window couldn't be read to tell its listeners which rows entered it, so they are told of each by its first shard:`, error);
|
|
1358
|
+
rows = named.flatMap((id) => {
|
|
1359
|
+
const head = byHead.get(id)?.[0];
|
|
1360
|
+
if (head)
|
|
1361
|
+
return [head];
|
|
1362
|
+
try {
|
|
1363
|
+
const row = cm.getMessage(id);
|
|
1364
|
+
return row ? [row] : [];
|
|
1365
|
+
}
|
|
1366
|
+
catch {
|
|
1367
|
+
return [];
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
for (const listener of [...listeners]) {
|
|
1372
|
+
try {
|
|
1373
|
+
listener({ agentName: agent.name, occasion, rows });
|
|
1374
|
+
}
|
|
1375
|
+
catch (error) {
|
|
1376
|
+
console.error(`[presentation] a listener of ${agent.name}'s window failed:`, error);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* A presentation that failed, logged and traced (presentation:failed):
|
|
1382
|
+
* `ids` are the rows it named that didn't enter the window (a body by
|
|
1383
|
+
* the id that names it), where they are known, and `next` says what
|
|
1384
|
+
* becomes of them.
|
|
1385
|
+
*/
|
|
1386
|
+
failed(agent, occasion, error, ids, next = "what it didn't present waits for the next point") {
|
|
1387
|
+
const left = ids !== undefined && ids.length > 0 ? [...ids] : undefined;
|
|
1388
|
+
const named = left
|
|
1389
|
+
? ` ${left.length} row${left.length === 1 ? '' : 's'} (${left.slice(0, 10).join(', ')}${left.length > 10 ? ', …' : ''})`
|
|
1390
|
+
: '';
|
|
1391
|
+
console.error(`[presentation] ${agent.name}: presenting${named} at ${occasion} failed; ${next}:`, error);
|
|
1392
|
+
this.host.emitTrace({
|
|
1393
|
+
type: 'presentation:failed', agentName: agent.name, occasion, error: describe(error), ...(left ? { messageIds: left } : {}),
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
/** Whether `cm`'s window reads as presented; a mode that can't be read is not. */
|
|
1398
|
+
function readsPresented(cm) {
|
|
1399
|
+
try {
|
|
1400
|
+
return cm.windowMode() === 'presented';
|
|
1401
|
+
}
|
|
1402
|
+
catch {
|
|
1403
|
+
return false;
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
/** The defined ids, each once, in order. */
|
|
1407
|
+
function unique(ids) {
|
|
1408
|
+
const seen = new Set();
|
|
1409
|
+
const out = [];
|
|
1410
|
+
for (const id of ids) {
|
|
1411
|
+
if (id === undefined || seen.has(id))
|
|
1412
|
+
continue;
|
|
1413
|
+
seen.add(id);
|
|
1414
|
+
out.push(id);
|
|
1415
|
+
}
|
|
1416
|
+
return out;
|
|
1417
|
+
}
|
|
1418
|
+
/** How many bodies a window holds, a sharded one counted once. */
|
|
1419
|
+
function bodyHeadCount(window) {
|
|
1420
|
+
let count = 0;
|
|
1421
|
+
for (let i = 0; i < window.length; i++)
|
|
1422
|
+
if (i === 0 || !continuesBody(window[i - 1], window[i]))
|
|
1423
|
+
count++;
|
|
1424
|
+
return count;
|
|
1425
|
+
}
|
|
1426
|
+
/** The ids of the last `count` bodies of a window, by their first shards, in window order. */
|
|
1427
|
+
function lastBodyHeads(window, count) {
|
|
1428
|
+
const heads = [];
|
|
1429
|
+
for (let i = window.length - 1; i >= 0 && heads.length < count; i--) {
|
|
1430
|
+
if (i === 0 || !continuesBody(window[i - 1], window[i]))
|
|
1431
|
+
heads.unshift(window[i].id);
|
|
1432
|
+
}
|
|
1433
|
+
return heads;
|
|
1434
|
+
}
|
|
1435
|
+
//# sourceMappingURL=presentation.js.map
|