@holoscript/framework 6.0.3
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/ALL-test-results.json +1 -0
- package/CHANGELOG.md +8 -0
- package/LICENSE +21 -0
- package/ROADMAP.md +175 -0
- package/dist/AgentManifest-CB4xM-Ma.d.cts +704 -0
- package/dist/AgentManifest-CB4xM-Ma.d.ts +704 -0
- package/dist/BehaviorTree-BrBFECv5.d.cts +103 -0
- package/dist/BehaviorTree-BrBFECv5.d.ts +103 -0
- package/dist/InvisibleWallet-BB6tFvRA.d.cts +1732 -0
- package/dist/InvisibleWallet-rtRrBOA8.d.ts +1732 -0
- package/dist/OrchestratorAgent-BvWgf9uw.d.cts +798 -0
- package/dist/OrchestratorAgent-Q_CbVTmO.d.ts +798 -0
- package/dist/agents/index.cjs +4790 -0
- package/dist/agents/index.d.cts +1788 -0
- package/dist/agents/index.d.ts +1788 -0
- package/dist/agents/index.js +4695 -0
- package/dist/ai/index.cjs +5347 -0
- package/dist/ai/index.d.cts +1753 -0
- package/dist/ai/index.d.ts +1753 -0
- package/dist/ai/index.js +5244 -0
- package/dist/behavior.cjs +449 -0
- package/dist/behavior.d.cts +130 -0
- package/dist/behavior.d.ts +130 -0
- package/dist/behavior.js +407 -0
- package/dist/economy/index.cjs +3659 -0
- package/dist/economy/index.d.cts +747 -0
- package/dist/economy/index.d.ts +747 -0
- package/dist/economy/index.js +3617 -0
- package/dist/implementations-D9T3un9D.d.cts +236 -0
- package/dist/implementations-D9T3un9D.d.ts +236 -0
- package/dist/index.cjs +24550 -0
- package/dist/index.d.cts +1729 -0
- package/dist/index.d.ts +1729 -0
- package/dist/index.js +24277 -0
- package/dist/learning/index.cjs +219 -0
- package/dist/learning/index.d.cts +104 -0
- package/dist/learning/index.d.ts +104 -0
- package/dist/learning/index.js +189 -0
- package/dist/negotiation/index.cjs +970 -0
- package/dist/negotiation/index.d.cts +610 -0
- package/dist/negotiation/index.d.ts +610 -0
- package/dist/negotiation/index.js +931 -0
- package/dist/skills/index.cjs +1118 -0
- package/dist/skills/index.d.cts +289 -0
- package/dist/skills/index.d.ts +289 -0
- package/dist/skills/index.js +1079 -0
- package/dist/swarm/index.cjs +5268 -0
- package/dist/swarm/index.d.cts +2433 -0
- package/dist/swarm/index.d.ts +2433 -0
- package/dist/swarm/index.js +5221 -0
- package/dist/training/index.cjs +2745 -0
- package/dist/training/index.d.cts +1734 -0
- package/dist/training/index.d.ts +1734 -0
- package/dist/training/index.js +2687 -0
- package/extract-failures.js +10 -0
- package/package.json +82 -0
- package/src/__tests__/bounty-marketplace.test.ts +374 -0
- package/src/__tests__/delegation.test.ts +144 -0
- package/src/__tests__/distributed-claimer.test.ts +147 -0
- package/src/__tests__/done-log-audit.test.ts +342 -0
- package/src/__tests__/framework.test.ts +865 -0
- package/src/__tests__/goal-synthesizer.test.ts +236 -0
- package/src/__tests__/presence.test.ts +223 -0
- package/src/__tests__/protocol-agent.test.ts +254 -0
- package/src/__tests__/revenue-splitter.test.ts +114 -0
- package/src/__tests__/scenario-driven-todo.test.ts +197 -0
- package/src/__tests__/self-improve.test.ts +349 -0
- package/src/__tests__/service-lifecycle.test.ts +237 -0
- package/src/__tests__/skill-router.test.ts +121 -0
- package/src/agents/AgentManifest.ts +493 -0
- package/src/agents/AgentRegistry.ts +475 -0
- package/src/agents/AgentTypes.ts +585 -0
- package/src/agents/AgentWalletRegistry.ts +83 -0
- package/src/agents/AuthenticatedCRDT.ts +388 -0
- package/src/agents/CapabilityMatcher.ts +453 -0
- package/src/agents/CrossRealityHandoff.ts +305 -0
- package/src/agents/CulturalMemory.ts +454 -0
- package/src/agents/FederatedRegistryAdapter.ts +429 -0
- package/src/agents/NormEngine.ts +450 -0
- package/src/agents/OrchestratorAgent.ts +414 -0
- package/src/agents/SkillWorkflowEngine.ts +472 -0
- package/src/agents/TaskDelegationService.ts +551 -0
- package/src/agents/__tests__/AgentManifest.prod.test.ts +134 -0
- package/src/agents/__tests__/AgentManifest.test.ts +182 -0
- package/src/agents/__tests__/AgentModule.test.ts +864 -0
- package/src/agents/__tests__/AgentRegistry.prod.test.ts +125 -0
- package/src/agents/__tests__/AgentRegistry.test.ts +148 -0
- package/src/agents/__tests__/AgentTypes.test.ts +534 -0
- package/src/agents/__tests__/AgentWalletRegistry.test.ts +152 -0
- package/src/agents/__tests__/AuthenticatedCRDT.test.ts +558 -0
- package/src/agents/__tests__/CapabilityMatcher.prod.test.ts +117 -0
- package/src/agents/__tests__/CapabilityMatcher.test.ts +178 -0
- package/src/agents/__tests__/CrossRealityHandoff.test.ts +402 -0
- package/src/agents/__tests__/CulturalMemory.test.ts +200 -0
- package/src/agents/__tests__/FederatedRegistryAdapter.test.ts +409 -0
- package/src/agents/__tests__/NormEngine.test.ts +276 -0
- package/src/agents/__tests__/OrchestratorAgent.test.ts +182 -0
- package/src/agents/__tests__/SkillWorkflowEngine.test.ts +357 -0
- package/src/agents/__tests__/TaskDelegationService.test.ts +446 -0
- package/src/agents/index.ts +107 -0
- package/src/agents/spatial-comms/Layer1RealTime.ts +621 -0
- package/src/agents/spatial-comms/Layer2A2A.ts +661 -0
- package/src/agents/spatial-comms/Layer3MCP.ts +651 -0
- package/src/agents/spatial-comms/ProtocolTypes.ts +543 -0
- package/src/agents/spatial-comms/SpatialCommClient.ts +483 -0
- package/src/agents/spatial-comms/__tests__/performance-benchmark.test.ts +465 -0
- package/src/agents/spatial-comms/examples/multi-agent-world-creation.ts +409 -0
- package/src/agents/spatial-comms/index.ts +66 -0
- package/src/ai/AIAdapter.ts +313 -0
- package/src/ai/AICopilot.ts +331 -0
- package/src/ai/AIOutputValidator.ts +203 -0
- package/src/ai/BTNodes.ts +239 -0
- package/src/ai/BehaviorSelector.ts +135 -0
- package/src/ai/BehaviorTree.ts +153 -0
- package/src/ai/Blackboard.ts +165 -0
- package/src/ai/GenerationAnalytics.ts +461 -0
- package/src/ai/GenerationCache.ts +265 -0
- package/src/ai/GoalPlanner.ts +165 -0
- package/src/ai/HoloScriptGenerator.ts +580 -0
- package/src/ai/InfluenceMap.ts +180 -0
- package/src/ai/NavMesh.ts +168 -0
- package/src/ai/PerceptionSystem.ts +178 -0
- package/src/ai/PromptTemplates.ts +453 -0
- package/src/ai/SemanticSearchService.ts +80 -0
- package/src/ai/StateMachine.ts +196 -0
- package/src/ai/SteeringBehavior.ts +150 -0
- package/src/ai/SteeringBehaviors.ts +244 -0
- package/src/ai/TrainingDataGenerator.ts +1082 -0
- package/src/ai/UtilityAI.ts +145 -0
- package/src/ai/__tests__/AIAdapter.prod.test.ts +259 -0
- package/src/ai/__tests__/AIAdapter.test.ts +109 -0
- package/src/ai/__tests__/AICopilot.prod.test.ts +341 -0
- package/src/ai/__tests__/AICopilot.test.ts +178 -0
- package/src/ai/__tests__/AIOutputValidator.prod.test.ts +226 -0
- package/src/ai/__tests__/AIOutputValidator.test.ts +138 -0
- package/src/ai/__tests__/BTNodes.prod.test.ts +391 -0
- package/src/ai/__tests__/BTNodes.test.ts +263 -0
- package/src/ai/__tests__/BehaviorSelector.prod.test.ts +129 -0
- package/src/ai/__tests__/BehaviorSelector.test.ts +132 -0
- package/src/ai/__tests__/BehaviorTree.prod.test.ts +266 -0
- package/src/ai/__tests__/BehaviorTree.test.ts +216 -0
- package/src/ai/__tests__/Blackboard.prod.test.ts +339 -0
- package/src/ai/__tests__/Blackboard.test.ts +183 -0
- package/src/ai/__tests__/GenerationAnalytics.prod.test.ts +141 -0
- package/src/ai/__tests__/GenerationAnalytics.test.ts +165 -0
- package/src/ai/__tests__/GenerationCache.prod.test.ts +144 -0
- package/src/ai/__tests__/GenerationCache.test.ts +171 -0
- package/src/ai/__tests__/GoalPlanner.prod.test.ts +189 -0
- package/src/ai/__tests__/GoalPlanner.test.ts +137 -0
- package/src/ai/__tests__/GoalPlannerDepth.prod.test.ts +217 -0
- package/src/ai/__tests__/HoloScriptGenerator.test.ts +125 -0
- package/src/ai/__tests__/InfluenceMap.prod.test.ts +146 -0
- package/src/ai/__tests__/InfluenceMap.test.ts +149 -0
- package/src/ai/__tests__/NavMesh.prod.test.ts +141 -0
- package/src/ai/__tests__/NavMesh.test.ts +159 -0
- package/src/ai/__tests__/PerceptionSystem.prod.test.ts +135 -0
- package/src/ai/__tests__/PerceptionSystem.test.ts +250 -0
- package/src/ai/__tests__/PromptTemplates.prod.test.ts +313 -0
- package/src/ai/__tests__/PromptTemplates.test.ts +146 -0
- package/src/ai/__tests__/SemanticSearch.test.ts +37 -0
- package/src/ai/__tests__/StateMachine.prod.test.ts +162 -0
- package/src/ai/__tests__/StateMachine.test.ts +163 -0
- package/src/ai/__tests__/SteeringBehavior.prod.test.ts +251 -0
- package/src/ai/__tests__/SteeringBehavior.test.ts +135 -0
- package/src/ai/__tests__/SteeringBehaviors.prod.test.ts +133 -0
- package/src/ai/__tests__/SteeringBehaviors.test.ts +151 -0
- package/src/ai/__tests__/TrainingDataGenerator.prod.test.ts +286 -0
- package/src/ai/__tests__/TrainingDataGenerator.test.ts +286 -0
- package/src/ai/__tests__/UtilityAI.prod.test.ts +207 -0
- package/src/ai/__tests__/UtilityAI.test.ts +155 -0
- package/src/ai/__tests__/adapters.prod.test.ts +263 -0
- package/src/ai/__tests__/adapters.test.ts +320 -0
- package/src/ai/adapters.ts +1585 -0
- package/src/ai/index.ts +130 -0
- package/src/behavior/BehaviorPresets.ts +140 -0
- package/src/behavior/BehaviorTree.ts +236 -0
- package/src/behavior/StateMachine.ts +176 -0
- package/src/behavior/StateTrait.ts +67 -0
- package/src/behavior/index.ts +8 -0
- package/src/behavior.ts +8 -0
- package/src/board/audit.ts +284 -0
- package/src/board/board-ops.ts +336 -0
- package/src/board/board-types.ts +302 -0
- package/src/board/index.ts +69 -0
- package/src/define-agent.ts +46 -0
- package/src/define-team.ts +33 -0
- package/src/delegation.ts +265 -0
- package/src/distributed-claimer.ts +228 -0
- package/src/economy/AgentBudgetEnforcer.ts +464 -0
- package/src/economy/BountyManager.ts +185 -0
- package/src/economy/CreatorRevenueAggregator.ts +460 -0
- package/src/economy/InvisibleWallet.ts +82 -0
- package/src/economy/KnowledgeMarketplace.ts +193 -0
- package/src/economy/PaymentWebhookService.ts +512 -0
- package/src/economy/RevenueSplitter.ts +156 -0
- package/src/economy/SubscriptionManager.ts +546 -0
- package/src/economy/UnifiedBudgetOptimizer.ts +635 -0
- package/src/economy/UsageMeter.ts +440 -0
- package/src/economy/_core-stubs.ts +219 -0
- package/src/economy/index.ts +100 -0
- package/src/economy/x402-facilitator.ts +1978 -0
- package/src/index.ts +348 -0
- package/src/knowledge/__tests__/knowledge-consolidator.test.ts +444 -0
- package/src/knowledge/__tests__/knowledge-store-vector.test.ts +291 -0
- package/src/knowledge/brain.ts +167 -0
- package/src/knowledge/consolidation.ts +581 -0
- package/src/knowledge/knowledge-consolidator.ts +510 -0
- package/src/knowledge/knowledge-store.ts +616 -0
- package/src/learning/MemoryConsolidator.ts +102 -0
- package/src/learning/MemoryScorer.ts +69 -0
- package/src/learning/ProceduralCompiler.ts +45 -0
- package/src/learning/SemanticClusterer.ts +66 -0
- package/src/learning/index.ts +8 -0
- package/src/llm/llm-adapter.ts +159 -0
- package/src/mesh/index.ts +309 -0
- package/src/negotiation/NegotiationProtocol.ts +694 -0
- package/src/negotiation/NegotiationTypes.ts +473 -0
- package/src/negotiation/VotingMechanisms.ts +691 -0
- package/src/negotiation/index.ts +49 -0
- package/src/protocol/goal-synthesizer.ts +317 -0
- package/src/protocol/implementations.ts +474 -0
- package/src/protocol/micro-phase-decomposer.ts +299 -0
- package/src/protocol/micro-step-decomposer.test.ts +306 -0
- package/src/protocol-agent.test.ts +353 -0
- package/src/protocol-agent.ts +670 -0
- package/src/self-improve/absorb-scanner.ts +252 -0
- package/src/self-improve/evolution-engine.ts +149 -0
- package/src/self-improve/framework-absorber.ts +214 -0
- package/src/self-improve/index.ts +50 -0
- package/src/self-improve/prompt-optimizer.ts +212 -0
- package/src/self-improve/test-generator.ts +175 -0
- package/src/skill-router.ts +186 -0
- package/src/skills/index.ts +5 -0
- package/src/skills/skill-md-bridge.ts +1699 -0
- package/src/swarm/ACOEngine.ts +261 -0
- package/src/swarm/CollectiveIntelligence.ts +383 -0
- package/src/swarm/ContributionSynthesizer.ts +481 -0
- package/src/swarm/LeaderElection.ts +393 -0
- package/src/swarm/PSOEngine.ts +206 -0
- package/src/swarm/QuorumPolicy.ts +173 -0
- package/src/swarm/SwarmCoordinator.ts +335 -0
- package/src/swarm/SwarmManager.ts +442 -0
- package/src/swarm/SwarmMembership.ts +456 -0
- package/src/swarm/VotingRound.ts +255 -0
- package/src/swarm/__tests__/ACOEngine.prod.test.ts +164 -0
- package/src/swarm/__tests__/ACOEngine.test.ts +117 -0
- package/src/swarm/__tests__/CollectiveIntelligence.prod.test.ts +296 -0
- package/src/swarm/__tests__/CollectiveIntelligence.test.ts +457 -0
- package/src/swarm/__tests__/ContributionSynthesizer.prod.test.ts +269 -0
- package/src/swarm/__tests__/ContributionSynthesizer.test.ts +254 -0
- package/src/swarm/__tests__/LeaderElection.prod.test.ts +196 -0
- package/src/swarm/__tests__/LeaderElection.test.ts +151 -0
- package/src/swarm/__tests__/PSOEngine.prod.test.ts +162 -0
- package/src/swarm/__tests__/PSOEngine.test.ts +106 -0
- package/src/swarm/__tests__/QuorumPolicy.prod.test.ts +216 -0
- package/src/swarm/__tests__/QuorumPolicy.test.ts +177 -0
- package/src/swarm/__tests__/SwarmCoordinator.prod.test.ts +186 -0
- package/src/swarm/__tests__/SwarmCoordinator.test.ts +167 -0
- package/src/swarm/__tests__/SwarmManager.prod.test.ts +308 -0
- package/src/swarm/__tests__/SwarmManager.test.ts +373 -0
- package/src/swarm/__tests__/SwarmMembership.prod.test.ts +273 -0
- package/src/swarm/__tests__/SwarmMembership.test.ts +264 -0
- package/src/swarm/__tests__/VotingRound.prod.test.ts +233 -0
- package/src/swarm/__tests__/VotingRound.test.ts +174 -0
- package/src/swarm/analytics/SwarmInspector.ts +476 -0
- package/src/swarm/analytics/SwarmMetrics.ts +449 -0
- package/src/swarm/analytics/__tests__/SwarmInspector.prod.test.ts +366 -0
- package/src/swarm/analytics/__tests__/SwarmInspector.test.ts +454 -0
- package/src/swarm/analytics/__tests__/SwarmMetrics.prod.test.ts +254 -0
- package/src/swarm/analytics/__tests__/SwarmMetrics.test.ts +370 -0
- package/src/swarm/analytics/index.ts +7 -0
- package/src/swarm/index.ts +69 -0
- package/src/swarm/messaging/BroadcastChannel.ts +509 -0
- package/src/swarm/messaging/GossipProtocol.ts +565 -0
- package/src/swarm/messaging/SwarmEventBus.ts +443 -0
- package/src/swarm/messaging/__tests__/BroadcastChannel.prod.test.ts +331 -0
- package/src/swarm/messaging/__tests__/BroadcastChannel.test.ts +333 -0
- package/src/swarm/messaging/__tests__/GossipProtocol.prod.test.ts +356 -0
- package/src/swarm/messaging/__tests__/GossipProtocol.test.ts +437 -0
- package/src/swarm/messaging/__tests__/SwarmEventBus.prod.test.ts +191 -0
- package/src/swarm/messaging/__tests__/SwarmEventBus.test.ts +247 -0
- package/src/swarm/messaging/index.ts +8 -0
- package/src/swarm/spatial/FlockingBehavior.ts +462 -0
- package/src/swarm/spatial/FormationController.ts +500 -0
- package/src/swarm/spatial/Vector3.ts +170 -0
- package/src/swarm/spatial/ZoneClaiming.ts +509 -0
- package/src/swarm/spatial/__tests__/FlockingBehavior.prod.test.ts +239 -0
- package/src/swarm/spatial/__tests__/FlockingBehavior.test.ts +298 -0
- package/src/swarm/spatial/__tests__/FormationController.prod.test.ts +240 -0
- package/src/swarm/spatial/__tests__/FormationController.test.ts +297 -0
- package/src/swarm/spatial/__tests__/Vector3.prod.test.ts +283 -0
- package/src/swarm/spatial/__tests__/Vector3.test.ts +224 -0
- package/src/swarm/spatial/__tests__/ZoneClaiming.prod.test.ts +246 -0
- package/src/swarm/spatial/__tests__/ZoneClaiming.test.ts +374 -0
- package/src/swarm/spatial/index.ts +28 -0
- package/src/team.ts +1245 -0
- package/src/training/LRScheduler.ts +377 -0
- package/src/training/QualityScoringPipeline.ts +139 -0
- package/src/training/SoftDedup.ts +461 -0
- package/src/training/SparsityMonitor.ts +685 -0
- package/src/training/SparsityMonitorTypes.ts +209 -0
- package/src/training/SpatialTrainingDataGenerator.ts +1526 -0
- package/src/training/SpatialTrainingDataTypes.ts +216 -0
- package/src/training/TrainingPipelineConfig.ts +215 -0
- package/src/training/constants.ts +94 -0
- package/src/training/index.ts +138 -0
- package/src/training/schema.ts +147 -0
- package/src/training/scripts/generate-novel-use-cases-dataset.ts +272 -0
- package/src/training/scripts/generate-spatial-dataset.ts +521 -0
- package/src/training/training/data/novel-use-cases.jsonl +153 -0
- package/src/training/training/data/spatial-reasoning-10k.jsonl +9354 -0
- package/src/training/trainingmonkey/TrainingMonkeyIntegration.ts +477 -0
- package/src/training/trainingmonkey/TrainingMonkeyTypes.ts +230 -0
- package/src/training/trainingmonkey/index.ts +26 -0
- package/src/training/trait-mappings.ts +157 -0
- package/src/types/core-stubs.d.ts +113 -0
- package/src/types.ts +304 -0
- package/test-output.txt +0 -0
- package/test-result.json +1 -0
- package/tsc-errors.txt +4 -0
- package/tsc_output.txt +0 -0
- package/tsconfig.json +14 -0
- package/tsup-learning-esm.config.ts +12 -0
- package/tsup.config.ts +21 -0
- package/typescript-errors-2.txt +0 -0
- package/typescript-errors.txt +22 -0
- package/vitest-log-utf8.txt +268 -0
- package/vitest-log.txt +0 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GossipProtocol - Epidemic-style message propagation
|
|
3
|
+
* HoloScript v3.2 - Autonomous Agent Swarms
|
|
4
|
+
*
|
|
5
|
+
* Implements gossip-based message spreading for decentralized swarms
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Gossip message wrapper
|
|
10
|
+
*/
|
|
11
|
+
export interface IGossipMessage {
|
|
12
|
+
id: string;
|
|
13
|
+
originId: string;
|
|
14
|
+
content: unknown;
|
|
15
|
+
type: 'data' | 'heartbeat' | 'membership' | 'custom';
|
|
16
|
+
version: number;
|
|
17
|
+
createdAt: number;
|
|
18
|
+
ttl: number;
|
|
19
|
+
hops: number;
|
|
20
|
+
path: string[];
|
|
21
|
+
signature?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Peer information
|
|
26
|
+
*/
|
|
27
|
+
export interface IGossipPeer {
|
|
28
|
+
id: string;
|
|
29
|
+
address: string;
|
|
30
|
+
lastSeen: number;
|
|
31
|
+
failureCount: number;
|
|
32
|
+
isActive: boolean;
|
|
33
|
+
metadata?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Gossip protocol configuration
|
|
38
|
+
*/
|
|
39
|
+
export interface IGossipConfig {
|
|
40
|
+
/** Number of peers to gossip to per round */
|
|
41
|
+
fanout: number;
|
|
42
|
+
/** Gossip interval in milliseconds */
|
|
43
|
+
gossipInterval: number;
|
|
44
|
+
/** Maximum TTL for messages */
|
|
45
|
+
maxTTL: number;
|
|
46
|
+
/** Maximum hops per message */
|
|
47
|
+
maxHops: number;
|
|
48
|
+
/** Failure threshold before marking peer inactive */
|
|
49
|
+
failureThreshold: number;
|
|
50
|
+
/** Time before peer considered stale */
|
|
51
|
+
peerTimeout: number;
|
|
52
|
+
/** Whether to deduplicate messages */
|
|
53
|
+
deduplicate: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Message handler for gossip
|
|
58
|
+
*/
|
|
59
|
+
export type GossipHandler = (message: IGossipMessage, from: string) => void | Promise<void>;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Peer selector strategy
|
|
63
|
+
*/
|
|
64
|
+
export type PeerSelector = (
|
|
65
|
+
peers: IGossipPeer[],
|
|
66
|
+
count: number,
|
|
67
|
+
exclude?: string[]
|
|
68
|
+
) => IGossipPeer[];
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* GossipProtocol - Epidemic-style message propagation
|
|
72
|
+
*/
|
|
73
|
+
export class GossipProtocol {
|
|
74
|
+
readonly nodeId: string;
|
|
75
|
+
|
|
76
|
+
private peers: Map<string, IGossipPeer> = new Map();
|
|
77
|
+
private seenMessages: Map<string, number> = new Map();
|
|
78
|
+
private messageQueue: IGossipMessage[] = [];
|
|
79
|
+
private handlers: Map<string, GossipHandler[]> = new Map();
|
|
80
|
+
private config: IGossipConfig;
|
|
81
|
+
private running = false;
|
|
82
|
+
private gossipTimer: ReturnType<typeof setInterval> | null = null;
|
|
83
|
+
private peerSelector: PeerSelector;
|
|
84
|
+
private nextMsgId = 1;
|
|
85
|
+
|
|
86
|
+
// Statistics
|
|
87
|
+
private stats = {
|
|
88
|
+
messagesSent: 0,
|
|
89
|
+
messagesReceived: 0,
|
|
90
|
+
messagesDropped: 0,
|
|
91
|
+
gossipRounds: 0,
|
|
92
|
+
duplicatesIgnored: 0,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
constructor(nodeId: string, config?: Partial<IGossipConfig>) {
|
|
96
|
+
this.nodeId = nodeId;
|
|
97
|
+
this.config = {
|
|
98
|
+
fanout: 3,
|
|
99
|
+
gossipInterval: 1000,
|
|
100
|
+
maxTTL: 30000,
|
|
101
|
+
maxHops: 10,
|
|
102
|
+
failureThreshold: 3,
|
|
103
|
+
peerTimeout: 30000,
|
|
104
|
+
deduplicate: true,
|
|
105
|
+
...config,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Default random peer selection
|
|
109
|
+
this.peerSelector = this.randomPeerSelection.bind(this);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Start the gossip protocol
|
|
114
|
+
*/
|
|
115
|
+
start(): void {
|
|
116
|
+
if (this.running) return;
|
|
117
|
+
|
|
118
|
+
this.running = true;
|
|
119
|
+
this.gossipTimer = setInterval(() => {
|
|
120
|
+
this.gossipRound();
|
|
121
|
+
}, this.config.gossipInterval);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Stop the gossip protocol
|
|
126
|
+
*/
|
|
127
|
+
stop(): void {
|
|
128
|
+
this.running = false;
|
|
129
|
+
if (this.gossipTimer) {
|
|
130
|
+
clearInterval(this.gossipTimer);
|
|
131
|
+
this.gossipTimer = null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Check if protocol is running
|
|
137
|
+
*/
|
|
138
|
+
isRunning(): boolean {
|
|
139
|
+
return this.running;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Add a peer
|
|
144
|
+
*/
|
|
145
|
+
addPeer(id: string, address: string, metadata?: Record<string, unknown>): void {
|
|
146
|
+
if (id === this.nodeId) return;
|
|
147
|
+
|
|
148
|
+
this.peers.set(id, {
|
|
149
|
+
id,
|
|
150
|
+
address,
|
|
151
|
+
lastSeen: Date.now(),
|
|
152
|
+
failureCount: 0,
|
|
153
|
+
isActive: true,
|
|
154
|
+
metadata,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Remove a peer
|
|
160
|
+
*/
|
|
161
|
+
removePeer(id: string): boolean {
|
|
162
|
+
return this.peers.delete(id);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get a peer
|
|
167
|
+
*/
|
|
168
|
+
getPeer(id: string): IGossipPeer | undefined {
|
|
169
|
+
return this.peers.get(id);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get all active peers
|
|
174
|
+
*/
|
|
175
|
+
getActivePeers(): IGossipPeer[] {
|
|
176
|
+
return [...this.peers.values()].filter((p) => p.isActive);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get all peers
|
|
181
|
+
*/
|
|
182
|
+
getAllPeers(): IGossipPeer[] {
|
|
183
|
+
return [...this.peers.values()];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Publish a message to the gossip network
|
|
188
|
+
*/
|
|
189
|
+
publish(
|
|
190
|
+
content: unknown,
|
|
191
|
+
type: IGossipMessage['type'] = 'data',
|
|
192
|
+
options: { ttl?: number; signature?: string } = {}
|
|
193
|
+
): string {
|
|
194
|
+
const messageId = `gossip-${this.nodeId}-${this.nextMsgId++}-${Date.now()}`;
|
|
195
|
+
|
|
196
|
+
const message: IGossipMessage = {
|
|
197
|
+
id: messageId,
|
|
198
|
+
originId: this.nodeId,
|
|
199
|
+
content,
|
|
200
|
+
type,
|
|
201
|
+
version: 1,
|
|
202
|
+
createdAt: Date.now(),
|
|
203
|
+
ttl: options.ttl ?? this.config.maxTTL,
|
|
204
|
+
hops: 0,
|
|
205
|
+
path: [this.nodeId],
|
|
206
|
+
signature: options.signature,
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// Add to queue for gossip
|
|
210
|
+
this.messageQueue.push(message);
|
|
211
|
+
this.seenMessages.set(messageId, Date.now());
|
|
212
|
+
|
|
213
|
+
// Also emit locally (async fire-and-forget to allow synchronous handler invocation)
|
|
214
|
+
// Also emit locally - handlers are called synchronously
|
|
215
|
+
this.emitToHandlers(message, this.nodeId).catch(() => {});
|
|
216
|
+
|
|
217
|
+
this.stats.messagesSent++;
|
|
218
|
+
return messageId;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Receive a message from a peer
|
|
223
|
+
*/
|
|
224
|
+
async receive(message: IGossipMessage, fromPeerId: string): Promise<boolean> {
|
|
225
|
+
// Update peer last seen
|
|
226
|
+
const peer = this.peers.get(fromPeerId);
|
|
227
|
+
if (peer) {
|
|
228
|
+
peer.lastSeen = Date.now();
|
|
229
|
+
peer.failureCount = 0;
|
|
230
|
+
peer.isActive = true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Check if we've seen this message
|
|
234
|
+
if (this.config.deduplicate && this.seenMessages.has(message.id)) {
|
|
235
|
+
this.stats.duplicatesIgnored++;
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Check TTL
|
|
240
|
+
if (Date.now() - message.createdAt > message.ttl) {
|
|
241
|
+
this.stats.messagesDropped++;
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Check hop limit
|
|
246
|
+
if (message.hops >= this.config.maxHops) {
|
|
247
|
+
this.stats.messagesDropped++;
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Mark as seen
|
|
252
|
+
this.seenMessages.set(message.id, Date.now());
|
|
253
|
+
this.stats.messagesReceived++;
|
|
254
|
+
|
|
255
|
+
// Emit to handlers
|
|
256
|
+
await this.emitToHandlers(message, fromPeerId);
|
|
257
|
+
|
|
258
|
+
// Prepare for re-gossip
|
|
259
|
+
const forwardMessage: IGossipMessage = {
|
|
260
|
+
...message,
|
|
261
|
+
hops: message.hops + 1,
|
|
262
|
+
path: [...message.path, this.nodeId],
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// Add to queue for further gossip
|
|
266
|
+
this.messageQueue.push(forwardMessage);
|
|
267
|
+
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Subscribe to message types
|
|
273
|
+
*/
|
|
274
|
+
subscribe(type: IGossipMessage['type'] | '*', handler: GossipHandler): () => void {
|
|
275
|
+
const key = type === '*' ? '*' : type;
|
|
276
|
+
|
|
277
|
+
if (!this.handlers.has(key)) {
|
|
278
|
+
this.handlers.set(key, []);
|
|
279
|
+
}
|
|
280
|
+
this.handlers.get(key)!.push(handler);
|
|
281
|
+
|
|
282
|
+
return () => {
|
|
283
|
+
const handlers = this.handlers.get(key);
|
|
284
|
+
if (handlers) {
|
|
285
|
+
const idx = handlers.indexOf(handler);
|
|
286
|
+
if (idx >= 0) handlers.splice(idx, 1);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Set custom peer selector
|
|
293
|
+
*/
|
|
294
|
+
setPeerSelector(selector: PeerSelector): void {
|
|
295
|
+
this.peerSelector = selector;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Perform a gossip round
|
|
300
|
+
*/
|
|
301
|
+
async gossipRound(): Promise<void> {
|
|
302
|
+
if (!this.running) return;
|
|
303
|
+
|
|
304
|
+
this.stats.gossipRounds++;
|
|
305
|
+
|
|
306
|
+
// Clean old seen messages
|
|
307
|
+
this.cleanSeenMessages();
|
|
308
|
+
|
|
309
|
+
// Clean stale peers
|
|
310
|
+
this.cleanStalePeers();
|
|
311
|
+
|
|
312
|
+
// Get messages to gossip
|
|
313
|
+
const messages = this.messageQueue.splice(0);
|
|
314
|
+
if (messages.length === 0) return;
|
|
315
|
+
|
|
316
|
+
// Select peers for gossip
|
|
317
|
+
const activePeers = this.getActivePeers();
|
|
318
|
+
if (activePeers.length === 0) return;
|
|
319
|
+
|
|
320
|
+
const selectedPeers = this.peerSelector(
|
|
321
|
+
activePeers,
|
|
322
|
+
Math.min(this.config.fanout, activePeers.length)
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
// Gossip each message to selected peers
|
|
326
|
+
for (const message of messages) {
|
|
327
|
+
// Exclude peers already in the path
|
|
328
|
+
const validPeers = selectedPeers.filter((p) => !message.path.includes(p.id));
|
|
329
|
+
|
|
330
|
+
for (const peer of validPeers) {
|
|
331
|
+
try {
|
|
332
|
+
await this.sendToPeer(peer, message);
|
|
333
|
+
} catch {
|
|
334
|
+
// Record failure
|
|
335
|
+
peer.failureCount++;
|
|
336
|
+
if (peer.failureCount >= this.config.failureThreshold) {
|
|
337
|
+
peer.isActive = false;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Send a message to a peer (override for actual network)
|
|
346
|
+
*/
|
|
347
|
+
protected async sendToPeer(_peer: IGossipPeer, _message: IGossipMessage): Promise<void> {
|
|
348
|
+
// Override this method for actual network transport
|
|
349
|
+
// Default implementation does nothing (for testing)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Emit message to handlers
|
|
354
|
+
*/
|
|
355
|
+
private async emitToHandlers(message: IGossipMessage, from: string): Promise<void> {
|
|
356
|
+
// Emit to type-specific handlers
|
|
357
|
+
const typeHandlers = this.handlers.get(message.type);
|
|
358
|
+
const promises: Promise<void>[] = [];
|
|
359
|
+
if (typeHandlers) {
|
|
360
|
+
for (const handler of typeHandlers) {
|
|
361
|
+
try {
|
|
362
|
+
const result = handler(message, from);
|
|
363
|
+
if (result instanceof Promise) {
|
|
364
|
+
promises.push(result.catch(() => {}));
|
|
365
|
+
}
|
|
366
|
+
} catch {
|
|
367
|
+
// Continue on handler error
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Emit to wildcard handlers
|
|
373
|
+
const wildcardHandlers = this.handlers.get('*');
|
|
374
|
+
if (wildcardHandlers) {
|
|
375
|
+
for (const handler of wildcardHandlers) {
|
|
376
|
+
try {
|
|
377
|
+
const result = handler(message, from);
|
|
378
|
+
if (result instanceof Promise) {
|
|
379
|
+
promises.push(result.catch(() => {}));
|
|
380
|
+
}
|
|
381
|
+
} catch {
|
|
382
|
+
// Continue on handler error
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Wait for any async promises to complete (but don't block synchronous callers)
|
|
388
|
+
if (promises.length > 0) {
|
|
389
|
+
Promise.all(promises).catch(() => {});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Random peer selection
|
|
395
|
+
*/
|
|
396
|
+
private randomPeerSelection(
|
|
397
|
+
peers: IGossipPeer[],
|
|
398
|
+
count: number,
|
|
399
|
+
exclude: string[] = []
|
|
400
|
+
): IGossipPeer[] {
|
|
401
|
+
const available = peers.filter((p) => !exclude.includes(p.id));
|
|
402
|
+
const selected: IGossipPeer[] = [];
|
|
403
|
+
|
|
404
|
+
while (selected.length < count && available.length > 0) {
|
|
405
|
+
const idx = Math.floor(Math.random() * available.length);
|
|
406
|
+
selected.push(available.splice(idx, 1)[0]);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return selected;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Clean old seen messages
|
|
414
|
+
*/
|
|
415
|
+
private cleanSeenMessages(): void {
|
|
416
|
+
const now = Date.now();
|
|
417
|
+
for (const [id, timestamp] of this.seenMessages) {
|
|
418
|
+
if (now - timestamp > this.config.maxTTL) {
|
|
419
|
+
this.seenMessages.delete(id);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Clean stale peers
|
|
426
|
+
*/
|
|
427
|
+
private cleanStalePeers(): void {
|
|
428
|
+
const now = Date.now();
|
|
429
|
+
for (const peer of this.peers.values()) {
|
|
430
|
+
if (now - peer.lastSeen > this.config.peerTimeout) {
|
|
431
|
+
peer.isActive = false;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Send a heartbeat
|
|
438
|
+
*/
|
|
439
|
+
publishHeartbeat(metadata?: Record<string, unknown>): string {
|
|
440
|
+
return this.publish({ type: 'heartbeat', nodeId: this.nodeId, ...metadata }, 'heartbeat');
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Announce membership change
|
|
445
|
+
*/
|
|
446
|
+
publishMembership(action: 'join' | 'leave', metadata?: Record<string, unknown>): string {
|
|
447
|
+
return this.publish({ action, nodeId: this.nodeId, ...metadata }, 'membership');
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Get statistics
|
|
452
|
+
*/
|
|
453
|
+
getStats(): typeof this.stats & {
|
|
454
|
+
peerCount: number;
|
|
455
|
+
activePeerCount: number;
|
|
456
|
+
queueSize: number;
|
|
457
|
+
seenCount: number;
|
|
458
|
+
} {
|
|
459
|
+
return {
|
|
460
|
+
...this.stats,
|
|
461
|
+
peerCount: this.peers.size,
|
|
462
|
+
activePeerCount: this.getActivePeers().length,
|
|
463
|
+
queueSize: this.messageQueue.length,
|
|
464
|
+
seenCount: this.seenMessages.size,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Reset statistics
|
|
470
|
+
*/
|
|
471
|
+
resetStats(): void {
|
|
472
|
+
this.stats = {
|
|
473
|
+
messagesSent: 0,
|
|
474
|
+
messagesReceived: 0,
|
|
475
|
+
messagesDropped: 0,
|
|
476
|
+
gossipRounds: 0,
|
|
477
|
+
duplicatesIgnored: 0,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Get configuration
|
|
483
|
+
*/
|
|
484
|
+
getConfig(): IGossipConfig {
|
|
485
|
+
return { ...this.config };
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Anti-entropy sync for eventual consistency
|
|
491
|
+
*/
|
|
492
|
+
export class AntiEntropySync {
|
|
493
|
+
private nodeId: string;
|
|
494
|
+
private protocol: GossipProtocol;
|
|
495
|
+
private dataStore: Map<string, { value: unknown; version: number; timestamp: number }> =
|
|
496
|
+
new Map();
|
|
497
|
+
|
|
498
|
+
constructor(nodeId: string, protocol: GossipProtocol) {
|
|
499
|
+
this.nodeId = nodeId;
|
|
500
|
+
this.protocol = protocol;
|
|
501
|
+
|
|
502
|
+
// Subscribe to data messages
|
|
503
|
+
this.protocol.subscribe('data', (msg) => {
|
|
504
|
+
const data = msg.content as { key: string; value: unknown; version: number };
|
|
505
|
+
if (data.key && data.version !== undefined) {
|
|
506
|
+
this.handleSync(data.key, data.value, data.version, msg.createdAt);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Set a value (stores locally and gossips)
|
|
513
|
+
*/
|
|
514
|
+
set(key: string, value: unknown): void {
|
|
515
|
+
const existing = this.dataStore.get(key);
|
|
516
|
+
const version = existing ? existing.version + 1 : 1;
|
|
517
|
+
const timestamp = Date.now();
|
|
518
|
+
|
|
519
|
+
this.dataStore.set(key, { value, version, timestamp });
|
|
520
|
+
|
|
521
|
+
// Gossip the update
|
|
522
|
+
this.protocol.publish({ key, value, version }, 'data');
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Get a value
|
|
527
|
+
*/
|
|
528
|
+
get(key: string): unknown {
|
|
529
|
+
return this.dataStore.get(key)?.value;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Get all keys
|
|
534
|
+
*/
|
|
535
|
+
keys(): string[] {
|
|
536
|
+
return [...this.dataStore.keys()];
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Handle sync from gossip
|
|
541
|
+
*/
|
|
542
|
+
private handleSync(key: string, value: unknown, version: number, timestamp: number): void {
|
|
543
|
+
const existing = this.dataStore.get(key);
|
|
544
|
+
|
|
545
|
+
// LWW (Last Writer Wins) with version tiebreaker
|
|
546
|
+
if (
|
|
547
|
+
!existing ||
|
|
548
|
+
version > existing.version ||
|
|
549
|
+
(version === existing.version && timestamp > existing.timestamp)
|
|
550
|
+
) {
|
|
551
|
+
this.dataStore.set(key, { value, version, timestamp });
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Get data snapshot
|
|
557
|
+
*/
|
|
558
|
+
getSnapshot(): Map<string, unknown> {
|
|
559
|
+
const snapshot = new Map<string, unknown>();
|
|
560
|
+
for (const [key, data] of this.dataStore) {
|
|
561
|
+
snapshot.set(key, data.value);
|
|
562
|
+
}
|
|
563
|
+
return snapshot;
|
|
564
|
+
}
|
|
565
|
+
}
|